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

1. 概要

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

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

学習内容

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

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

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

Node.js アプリの作成に関するご経験はどの程度ありますか?

初心者 中級者 上級者

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

<ph type="x-smartling-placeholder"></ph> 初心者 中級 上達 をご覧ください。

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 USD 分の無料トライアル プログラムをご利用いただけます。

Google Cloud Shell

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

この Debian ベースの仮想マシンには、必要な開発ツールがすべて揃っています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働するため、ネットワークのパフォーマンスと認証が大幅に向上しています。つまり、この 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 コンソール ダッシュボードで確認します。

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

Service を実装する

まず、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. Service の使用

ジェネレータは、クライアント側のスタブをすべて生成済みです。このラボではわかりやすくするために、同じ 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 クライアントを実装する方法

次のステップ:

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