Java で gRPC サービスを構築する

1. 概要

gRPC は、Google で開発された言語やプラットフォームに依存しないリモート プロシージャ コール(RPC)フレームワークとツールセットです。特に強力なバイナリ シリアル化ツールセットと言語であるプロトコル バッファを使用してサービスを定義できます。これにより、さまざまな言語でサービス定義から慣用的なクライアント スタブとサーバー スタブを生成できます。

この Codelab では、gRPC フレームワークを使用して API を公開する Java ベースのサービスを構築し、生成された gRPC クライアントサイド スタブを使用するクライアントを作成する方法について学習します。

学習内容

  • プロトコル バッファ言語
  • Java を使用して gRPC サービスを実装する方法
  • Java を使用して gRPC クライアントを実装する方法

このチュートリアルをどのように使用されますか?

通読するのみ 通読し、演習を行う

Node.js アプリ作成のご経験についてお答えください。

初心者 中級者 上級者

Go アプリ作成のご経験についてお答えください。

初心者 中級者 上級者

2. 設定と要件

セルフペース型の環境設定

  1. Cloud コンソールにログインして、新しいプロジェクトを作成するか、既存のプロジェクトを再利用しますGmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

プロジェクト ID を忘れないようにしてください。プロジェクト ID はすべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているので使用できません)。以降、このコードラボでは PROJECT_ID と呼びます。

  1. 次に、Google Cloud リソースを使用するために、Cloud Console で課金を有効にする必要があります。

このコードラボを実行しても、費用はほとんどかからないはずです。このチュートリアル以外で請求が発生しないように、リソースのシャットダウン方法を説明する「クリーンアップ」セクションの手順に従うようにしてください。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Google Cloud Shell

この Codelab はパソコンから操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。

この Debian ベースの仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。つまり、この Codelab に必要なのはブラウザだけです(Chromebook でも動作します)。

  1. Cloud Console から Cloud Shell を有効にするには、[Cloud Shell をアクティブにする] a8460e837e9f5fda.png をクリックします(環境のプロビジョニングと接続に若干時間を要します)。

b532b2f19ab85dda.png

Screen 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 リクエストと Hello レスポンスを含むシンプルな Greeting Service の proto ファイルを作成しましょう。

まず、新しい 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 の依存関係とプラグインを追加する必要があります。

まず、gRPC の依存関係を pom.xml に追加します。

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>

スタブを生成する

アプリケーションをビルドすると、プラグインは proto 定義を 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. ストリーミング サービス

他にもさまざまな方法をお試しいただけます。たとえば、proto ファイルのリクエスト パラメータまたはレスポンス パラメータに 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);
}

サーバーを更新して、1 つだけでなく複数のレスポンスを送信するようにします。これを行うには、複数の 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 クライアントを実装する方法

次のステップ:

フィードバックをお寄せください