자바를 사용하여 gRPC 서비스 빌드

1. 개요

gRPC는 Google에서 개발한 언어 중립적이고 플랫폼 중립적인 리모트 프로시져 콜 (RPC) 프레임워크 및 도구 모음입니다. 특히 강력한 바이너리 직렬화 도구 집합 및 언어인 프로토콜 버퍼를 사용하여 서비스를 정의할 수 있습니다. 그런 다음 다양한 언어로 서비스 정의에서 관용적인 클라이언트 및 서버 스텁을 생성할 수 있습니다.

이 Codelab에서는 gRPC 프레임워크를 사용하여 API를 노출하는 Java 기반 서비스를 빌드한 다음 생성된 gRPC 클라이언트 측 스텁을 사용하는 클라이언트를 작성하는 방법을 알아봅니다.

학습할 내용

  • 프로토콜 버퍼 언어
  • Java를 사용하여 gRPC 서비스를 구현하는 방법
  • Java를 사용하여 gRPC 클라이언트를 구현하는 방법

이 튜토리얼을 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

Node.js 앱 빌드 관련 사전지식 수준을 평가해 주세요.

초급 중급 고급

Go 앱 빌드 경험을 평가해 주세요.

초급 중급 고급

2. 설정 및 요구사항

자습형 환경 설정

  1. Cloud 콘솔에 로그인하고 새 프로젝트를 만들거나 기존 프로젝트를 다시 사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.

96a9c957bc475304.pngb9a10ebdf5b5a448.pnga1e3c01a38fa61c2.png

모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID라고 부릅니다.

  1. 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.

이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 신규 사용자에게는 미화$300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.

Google Cloud Shell

이 Codelab은 컴퓨터에서 실행할 수 있지만, 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.

이 Debian 기반 가상 머신에는 필요한 모든 개발 도구가 로드되어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 즉, 이 Codelab에 필요한 것은 브라우저뿐입니다(Chromebook에서도 작동 가능).

  1. Cloud Console에서 Cloud Shell을 활성화하려면 단순히 Cloud Shell 활성화a8460e837e9f5fda.png를 클릭합니다. 환경을 프로비저닝하고 연결하는 데 몇 정도만 소요됩니다.

b532b2f19ab85dda.pngScreen Shot 2017-06-14 at 10.13.43 PM.png

Cloud Shell에 연결되면 사용자 인증이 이미 완료되었고 프로젝트가 내 PROJECT_ID에 설정되어 있음을 확인할 수 있습니다.

gcloud auth list

명령어 출력

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

명령어 출력

[core]
project = <PROJECT_ID>

어떤 이유로든 프로젝트가 설정되지 않았으면 다음 명령어를 실행하면 됩니다.

gcloud config set project <PROJECT_ID>

PROJECT_ID를 찾고 계신가요? 설정 단계에서 사용한 ID를 확인하거나 Cloud Console 대시보드에서 확인하세요.

2485e00c1223af09.png

또한 Cloud Shell은 기본적으로 이후 명령어를 실행할 때 유용할 수 있는 몇 가지 환경 변수를 설정합니다.

echo $GOOGLE_CLOUD_PROJECT

명령어 출력

<PROJECT_ID>
  1. 마지막으로 기본 영역 및 프로젝트 구성을 설정합니다.
gcloud config set compute/zone us-central1-f

다양한 영역을 선택할 수 있습니다. 자세한 내용은 리전 및 영역을 참조하세요.

3. gRPC 서비스 만들기

Maven으로 새 Java 프로젝트를 만듭니다.

$ mvn archetype:generate -DgroupId=com.example.grpc \
 -DartifactId=grpc-hello-server \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DinteractiveMode=false
$ cd grpc-hello-server

gRPC 정의 파일 추가

gRPC에서는 서비스 페이로드 (요청 및 응답)와 서비스 작업을 IDL (인터페이스 정의 언어)로 캡처해야 합니다. gRPC는 Protobuffer 3 문법을 사용하여 메시지 페이로드와 작업을 정의합니다. Hello Request와 Hello Response가 있는 간단한 Greeting Service의 프로토 파일을 만들어 보겠습니다.

먼저 새 proto 파일을 보관할 새 proto 디렉터리를 만듭니다.

$ mkdir -p src/main/proto

그런 다음 새 proto 파일 src/main/proto/GreetingService.proto을 만듭니다.

vim,nano, 또는 emacs을 사용하여 파일을 수정할 수 있습니다.

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

// Request payload
message HelloRequest {
  // Each message attribute is strongly typed.
  // You also must assign a "tag" number.
  // Each tag number is unique within the message.
  string name = 1;

  // This defines a strongly typed list of String
  repeated string hobbies = 2;

  // There are many more basics types, like Enum, Map
  // See https://developers.google.com/protocol-buffers/docs/proto3
  // for more information.
}

message HelloResponse {
  string greeting = 1;
}

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // Define a RPC operation
  rpc greeting(HelloRequest) returns (HelloResponse);
}

gRPC 종속 항목 및 플러그인 추가

정의가 있으면 이 파일에서 서버 측 스텁과 클라이언트 측 스텁을 모두 생성할 수 있습니다. gRPC 종속 항목과 플러그인을 추가해야 합니다.

먼저 pom.xml에 gRPC 종속 항목을 추가합니다.

pom.xml

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

그런 다음 플러그인을 추가합니다.

pom.xml

<project>
  ...
  <dependencies>
    ...
  </dependencies>
  <build>
    <extensions>
      <extension>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.6.2</version>
      </extension>
    </extensions>
    <plugins>
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:3.9.0:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.24.0:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

스텁 생성

애플리케이션을 빌드하면 플러그인이 프로토 정의를 Java 코드로 변환합니다.

$ mvn -DskipTests package

생성된 파일을 확인하려면 다음을 실행하세요.

$ find target/generated-sources

서비스 구현

먼저 greeting 작업을 구현할 새 GreetingServiceImpl 클래스를 만듭니다.

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
  // HelloRequest has toString auto-generated.
    System.out.println(request);

    // You must use a builder to construct a new Protobuffer object
    GreetingServiceOuterClass.HelloResponse response = GreetingServiceOuterClass.HelloResponse.newBuilder()
      .setGreeting("Hello there, " + request.getName())
      .build();

    // Use responseObserver to send a single response back
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

서버 구현

마지막으로 포트에서 수신 대기하고 이 서비스 구현을 등록하는 서버를 시작해야 합니다. App 클래스와 기본 메서드를 수정합니다.

src/main/java/com/example/grpc/App.java

package com.example.grpc;

import io.grpc.*;

public class App
{
    public static void main( String[] args ) throws Exception
    {
      // Create a new server to listen on port 8080
      Server server = ServerBuilder.forPort(8080)
        .addService(new GreetingServiceImpl())
        .build();

      // Start the server
      server.start();

      // Server threads are running in the background.
      System.out.println("Server started");
      // Don't exit the main thread. Wait until server is terminated.
      server.awaitTermination();
    }
}

마지막으로 서버를 실행합니다.

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

4. 서비스 사용

생성기가 이미 모든 클라이언트 측 스텁을 생성했습니다. 실습을 간단하게 진행하기 위해 동일한 Maven 프로젝트를 사용하되 새 main 메서드가 있는 새 Client 클래스를 추가합니다.

먼저 +를 클릭하여 새 Cloud Shell 세션을 엽니다. 이렇게 하면 서버를 종료하지 않아도 됩니다.

1ff0fda960b9adfc.png

새 세션에서 grpc-hello-server 디렉터리로 전환합니다.

$ cd grpc-hello-server

그런 다음 새 Client 클래스를 추가합니다.

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      // Channel is the abstraction to connect to a service endpoint
      // Let's use plaintext communication because we don't have certs
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // It is up to the client to determine whether to block the call
      // Here we create a blocking stub, but an async stub,
      // or an async stub with Future are always possible.
      GreetingServiceGrpc.GreetingServiceBlockingStub stub = GreetingServiceGrpc.newBlockingStub(channel);
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Finally, make the call using the stub
      GreetingServiceOuterClass.HelloResponse response = 
        stub.greeting(request);

      System.out.println(response);

      // A Channel should be shutdown before stopping the process.
      channel.shutdownNow();
    }
}

마지막으로 클라이언트를 실행합니다.

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"

작업이 끝났습니다. 간단하죠?

5. 스트리밍 서비스

이 외에도 시도해 볼 수 있는 것이 많습니다. 예를 들어 프로토 파일에서 요청 또는 응답 매개변수에 stream 키워드를 추가하여 스트리밍 서비스를 쉽게 빌드할 수 있습니다.

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

...

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // MODIFY HERE: Update the return to streaming return.
  rpc greeting(HelloRequest) returns (stream HelloResponse);
}

하나가 아닌 여러 응답을 전송하도록 서버를 업데이트합니다. 여러 responseObserver.onNext(...) 호출을 통해 이를 수행할 수 있습니다.

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
 
    ...

    // Feel free to construct different responses if you'd like.
    responseObserver.onNext(response);
    responseObserver.onNext(response);
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

클라이언트는 차단 스텁 대신 비동기 스텁을 사용해야 합니다. stub 유형을 GreetingServiceStub로 업데이트하여 클라이언트 코드를 업데이트합니다.

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

// New import
import io.grpc.stub.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // Replace the previous synchronous code with asynchronous code.
      // This time use an async stub:
       GreetingServiceGrpc.GreetingServiceStub stub = GreetingServiceGrpc.newStub(channel);

      // Construct a request
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Make an Asynchronous call. Listen to responses w/ StreamObserver
      stub.greeting(request, new StreamObserver<GreetingServiceOuterClass.HelloResponse>() {
        public void onNext(GreetingServiceOuterClass.HelloResponse response) {
          System.out.println(response);
        }
        public void onError(Throwable t) {
        }
        public void onCompleted() {
          // Typically you'll shutdown the channel somewhere else.
          // But for the purpose of the lab, we are only making a single
          // request. We'll shutdown as soon as this request is done.
          channel.shutdownNow();
        }
      });
    }
}

애플리케이션을 다시 빌드합니다.

$ mvn -DskipTests package

각각의 Cloud Shell 세션에서 서버와 클라이언트를 모두 다시 시작합니다.

서버를 시작하려면 다음 단계를 따르세요.

$ mvn exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

클라이언트를 시작하려면 다음을 실행합니다.

$ mvn exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"

6. 수고하셨습니다

학습한 내용:

  • 프로토콜 버퍼 언어
  • Java를 사용하여 gRPC 서버를 구현하는 방법
  • Java를 사용하여 gRPC 클라이언트를 구현하는 방법

다음 단계:

Google에 의견 보내기

  • 잠시 시간을 내어 간단한 설문조사에 응해주시기 바랍니다.