gRPC-Java スタートガイド

1. はじめに

この Codelab では、gRPC-Java を使用して、Java で記述されたルート マッピング アプリケーションの基盤となるクライアントとサーバーを作成します。

このチュートリアルを完了すると、gRPC を使用してリモート サーバーに接続し、地図上の特定の座標にあるものの名前または郵便番号を取得するクライアントが作成されます。本格的なアプリケーションでは、このクライアント サーバー設計を使用して、ルート上のスポットを列挙または要約できます。

サーバーの API は Protocol Buffers ファイルで定義されます。このファイルは、クライアントとサーバーが相互に通信できるように、クライアントとサーバーのボイラープレート コードを生成するために使用されます。これにより、その機能を実装する時間と労力を節約できます。

この生成されたコードは、サーバーとクライアント間の通信の複雑さだけでなく、データのシリアル化と逆シリアル化も処理します。

学習内容

  • プロトコル バッファを使用してサービス API を定義する方法。
  • 自動コード生成を使用して、プロトコル バッファ定義から gRPC ベースのクライアントとサーバーを構築する方法。
  • gRPC を使用したクライアント サーバー通信の理解。

この Codelab は、gRPC を初めて使用する Java デベロッパー、gRPC の復習を希望するデベロッパー、分散システムの構築に関心のある方を対象としています。gRPC の経験は必要ありません。

2. 始める前に

前提条件

  • JDK バージョン 8 以降

コードを取得する

この Codelab では、完全にゼロから始める必要がないように、アプリケーションのソースコードのスケルトンが用意されています。次の手順では、プロトコル バッファ コンパイラ プラグインを使用してボイラープレート gRPC コードを生成するなど、アプリケーションを完成させる方法について説明します。

まず、Codelab の作業ディレクトリを作成し、そのディレクトリに移動します。

mkdir grpc-java-getting-started && cd grpc-java-getting-started

Codelab をダウンロードして解凍します。

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-java-getting-started/start_here

または、Codelab ディレクトリのみを含む .zip ファイルをダウンロードして、手動で解凍することもできます。

実装の入力をスキップする場合は、完成したソースコードを GitHub で入手できます。

3. サービスを定義する

まず、プロトコル バッファを使用して、アプリケーションの gRPC サービス、RPC メソッド、リクエストとレスポンスのメッセージ タイプを定義します。サービスでは以下を提供します。

  • サーバーが実装し、クライアントが呼び出す GetFeature という RPC メソッド。
  • GetFeature メソッドを使用する際にクライアントとサーバー間で交換されるデータ構造であるメッセージ タイプ PointFeature。クライアントは、サーバーへの GetFeature リクエストで地図の座標を Point として提供し、サーバーはそれらの座標にあるものを説明する対応する Feature で応答します。

この RPC メソッドとそのメッセージ型はすべて、提供されたソースコードの src/main/proto/routeguide/route_guide.proto ファイルで定義されます。

Protocol Buffers は一般に protobuf と呼ばれます。gRPC の用語の詳細については、gRPC の コア コンセプト、アーキテクチャ、ライフサイクルをご覧ください。

この例では Java コードを生成するため、.protojava_package ファイル オプションと Java クラスの名前を指定しています。

option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";

メッセージのタイプ

ソースコードの routeguide/route_guide.proto ファイルで、まず Point メッセージ タイプを定義します。Point は、地図上の緯度と経度の座標ペアを表します。この Codelab では、座標に整数を使用します。

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

数値 12 は、message 構造内の各フィールドの一意の ID 番号です。

次に、Feature メッセージ タイプを定義します。Feature は、Point で指定された場所にあるものの名前または郵便番号に string フィールドを使用します。

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

サービス メソッド

route_guide.proto ファイルには、アプリケーションのサービスによって提供される 1 つ以上のメソッドを定義する RouteGuide という名前の service 構造があります。

RouteGuide 定義内に rpc メソッド GetFeature を追加します。前述のとおり、このメソッドは指定された座標セットから場所の名前または住所を検索するため、指定された Point に対して GetFeatureFeature を返すようにします。

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

これは単項 RPC メソッドです。クライアントがサーバーにリクエストを送信し、ローカル関数呼び出しと同様にレスポンスが返ってくるのを待つシンプルな RPC です。

4. クライアントとサーバーのコードを生成する

次に、.proto サービス定義から gRPC クライアントとサーバーのインターフェースを生成する必要があります。これを行うには、特別な gRPC Java プラグインを備えたプロトコル バッファ コンパイラ protoc を使用します。gRPC サービスを生成するには、proto3 コンパイラ(proto2 と proto3 の両方の構文をサポート)を使用する必要があります。

Gradle または Maven を使用する場合、protoc ビルドプラグインはビルドの一部として必要なコードを生成できます。独自の .proto ファイルからコードを生成する方法については、grpc-java README をご覧ください。

このプロジェクトをビルドするために、Codelab のソースコードに Gradle 環境と構成を用意しました。

grpc-java-getting-started ディレクトリ内で、次のコマンドを実行します。

$ chmod +x gradlew
$ ./gradlew generateProto

サービス定義から次のクラスが生成されます。

  • Feature.javaPoint.java など。リクエストとレスポンスのメッセージ タイプを設定、シリアル化、取得するためのすべてのプロトコル バッファ コードが含まれています。
  • RouteGuideGrpc.javaRouteGuide サーバーが実装する基本クラス RouteGuideGrpc.RouteGuideImplBase と、RouteGuide サービスで定義されたすべてのメソッド、クライアントが使用するスタブクラス(他の有用なコードも含む)が含まれています。

5. サーバーを実装する

まず、RouteGuide サーバーの作成方法を見てみましょう。RouteGuide サービスが機能するには、次の 2 つの要素が必要です。

  • サービス定義から生成されたサービス インターフェースを実装します。これは、サービスの実際の「作業」を行います。
  • gRPC サーバーを実行して、クライアントからのリクエストをリッスンし、適切なサービス実装にディスパッチします。

RouteGuide を実装する

ご覧のとおり、サーバーには、生成された RouteGuideGrpc.RouteGuideImplBase 抽象クラスを拡張する RouteGuideService クラスがあります。

private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
...
}

サーバーを機能で初期化するために、次の 2 つのファイルが用意されています。

./src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java

./src/main/resources/io/grpc/examples/routeguide/route_guide_db.json

簡単な RPC 実装について詳しく見ていきましょう。

単項 RPC

RouteGuideService は、すべてのサービス メソッドを実装します。この場合、GetFeature() は、クライアントから Point メッセージを受け取り、既知の場所のリストから対応する位置情報を Feature メッセージで返します。

@Override
public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
  responseObserver.onNext(checkFeature(request));
  responseObserver.onCompleted();
}

getFeature() メソッドは次の 2 つのパラメータを取ります。

  • Point: リクエスト。
  • StreamObserver<Feature>: レスポンス オブザーバー。サーバーがレスポンスとともに呼び出すための特別なインターフェースです。

クライアントに回答を返し、通話を終了するには:

  1. サービス定義で指定されているとおり、クライアントに返す Feature レスポンス オブジェクトを構築して入力します。この例では、別のプライベート checkFeature() メソッドでこれを行います。
  2. レスポンス オブザーバーの onNext() メソッドを使用して Feature を返します。
  3. レスポンス オブザーバーの onCompleted() メソッドを使用して、RPC の処理が完了したことを指定します。

サーバーを起動する

すべてのサービス メソッドを実装したら、クライアントが実際にサービスを使用できるように、gRPC サーバーを起動する必要があります。ボイラープレートには、ServerBuilder オブジェクトの作成が含まれています。

ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)

コンストラクタでサービスをビルドします。

  1. ビルダーの forPort() メソッドを使用して、クライアント リクエストをリッスンするために使用するポートを指定します(ワイルドカード アドレスが使用されます)。
  2. サービス実装クラス RouteGuideService のインスタンスを作成し、ビルダーの addService() メソッドに渡します。
  3. ビルダーで build() を呼び出して、サービスの RPC サーバーを作成します。

次のスニペットは、ServerBuilder オブジェクトを作成する方法を示しています。

/** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
public RouteGuideServer(int port, URL featureFile) throws IOException {
    this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()),
        port, RouteGuideUtil.parseFeatures(featureFile));
  }

次のスニペットは、RouteGuide サービスのサーバー オブジェクトを作成する方法を示しています。

/** Create a RouteGuide server using serverBuilder as a base and features as data. */
public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
  this.port = port;
  server = serverBuilder.addService(new RouteGuideService(features))
      .build();
}

上記で作成したサーバーで start を呼び出す start メソッドを実装します。

public void start() throws IOException {
  server.start();
  logger.info("Server started, listening on " + port);
}

サーバーが完了するまで待機するメソッドを実装して、すぐに終了しないようにします。

/** Await termination on the main thread since the grpc library uses daemon threads. */
private void blockUntilShutdown() throws InterruptedException {
  if (server != null) {
    server.awaitTermination();
  }
}

ご覧のとおり、ServerBuilder を使用してサーバーをビルドして起動します。

メインメソッドでは、次の処理を行います。

  1. RouteGuideServer インスタンスを作成します。
  2. start() を呼び出して、サービスの RPC サーバーを有効にします。
  3. blockUntilShutdown() を呼び出して、サービスが停止するまで待機します。
 public static void main(String[] args) throws Exception {
    RouteGuideServer server = new RouteGuideServer(8980);
    server.start();
    server.blockUntilShutdown();
  }

6. クライアントを作成する

このセクションでは、RouteGuide サービスのクライアントを作成する方法について説明します。

スタブをインスタンス化する

サービス メソッドを呼び出すには、まずスタブを作成する必要があります。スタブには 2 種類ありますが、この Codelab ではブロッキング スタブのみを使用します。次の 2 つのタイプがあります。

  • RPC 呼び出しを行い、サーバーからの応答を待機するブロッキング/同期スタブ。応答を返すか、例外を発生させます。
  • サーバーに非ブロック呼び出しを行う非ブロック/非同期スタブ。レスポンスは非同期で返されます。特定のタイプのストリーミング呼び出しは、非同期スタブを使用した場合にのみ行うことができます。

まず、gRPC チャネルを作成し、そのチャネルを使用してスタブを作成する必要があります。

ManagedChannelBuilder を直接使用してチャネルを作成することもできます。

ManagedChannelBuilder.forAddress(host, port).usePlaintext().build

ただし、hostname:port を含む文字列を受け取るユーティリティ メソッドを使用しましょう。

Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();

これで、このチャネルを使用してブロッキング スタブを作成できます。この Codelab ではブロッキング RPC のみを使用するため、.proto から生成した RouteGuideGrpc クラスで提供される newBlockingStub メソッドを使用します。

blockingStub = RouteGuideGrpc.newBlockingStub(channel);

サービス メソッドを呼び出す

次に、サービス メソッドを呼び出す方法を見てみましょう。

Simple RPC

シンプルな RPC GetFeature の呼び出しは、ローカル メソッドの呼び出しとほぼ同じくらい簡単です。

リクエスト プロトコル バッファ オブジェクト(この例では Point)を作成して入力し、ブロッキング スタブの getFeature() メソッドに渡して、Feature を取得します。

エラーが発生すると、Status としてエンコードされ、StatusRuntimeException から取得できます。

Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();

Feature feature;
try {
  feature = blockingStub.getFeature(request);
} catch (StatusRuntimeException e) {
  logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
  return;
}

ボイラープレートは、指定されたポイントに機能があったかどうかに基づいて、コンテンツを含むメッセージを記録します。

7. ぜひお試しください。

  1. start_here ディレクトリ内で、次のコマンドを実行します。
$ ./gradlew installDist

これにより、コードがコンパイルされ、jar にパッケージ化され、例を実行するスクリプトが作成されます。これらは build/install/start_here/bin/ ディレクトリに作成されます。スクリプトは route-guide-serverroute-guide-client です。

クライアントを起動する前に、サーバーが実行されている必要があります。

  1. サーバーを実行します。
$ ./build/install/start_here/bin/route-guide-server
  1. クライアントを実行します。
$ ./build/install/start_here/bin/route-guide-client

次のような出力が表示されます(わかりやすくするため、タイムスタンプは省略されています)。

INFO: *** GetFeature: lat=409,146,138 lon=-746,188,906
INFO: Found feature called "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" at 40.915, -74.619
INFO: *** GetFeature: lat=0 lon=0
INFO: Found no feature at 0, 0

8. 次のステップ