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
メソッドを使用する際にクライアントとサーバー間で交換されるデータ構造であるメッセージ タイプPoint
とFeature
。クライアントは、サーバーへのGetFeature
リクエストで地図の座標をPoint
として提供し、サーバーはそれらの座標にあるものを説明する対応するFeature
で応答します。
この RPC メソッドとそのメッセージ型はすべて、提供されたソースコードの src/main/proto/routeguide/route_guide.proto
ファイルで定義されます。
Protocol Buffers は一般に protobuf と呼ばれます。gRPC の用語の詳細については、gRPC の コア コンセプト、アーキテクチャ、ライフサイクルをご覧ください。
この例では Java コードを生成するため、.proto
で java_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;
}
数値 1
と 2
は、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
に対して GetFeature
が Feature
を返すようにします。
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.java
、Point.java
など。リクエストとレスポンスのメッセージ タイプを設定、シリアル化、取得するためのすべてのプロトコル バッファ コードが含まれています。RouteGuideGrpc.java
。RouteGuide
サーバーが実装する基本クラス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>
: レスポンス オブザーバー。サーバーがレスポンスとともに呼び出すための特別なインターフェースです。
クライアントに回答を返し、通話を終了するには:
- サービス定義で指定されているとおり、クライアントに返す
Feature
レスポンス オブジェクトを構築して入力します。この例では、別のプライベートcheckFeature()
メソッドでこれを行います。 - レスポンス オブザーバーの
onNext()
メソッドを使用してFeature
を返します。 - レスポンス オブザーバーの
onCompleted()
メソッドを使用して、RPC の処理が完了したことを指定します。
サーバーを起動する
すべてのサービス メソッドを実装したら、クライアントが実際にサービスを使用できるように、gRPC サーバーを起動する必要があります。ボイラープレートには、ServerBuilder オブジェクトの作成が含まれています。
ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)
コンストラクタでサービスをビルドします。
- ビルダーの
forPort()
メソッドを使用して、クライアント リクエストをリッスンするために使用するポートを指定します(ワイルドカード アドレスが使用されます)。 - サービス実装クラス
RouteGuideService
のインスタンスを作成し、ビルダーのaddService()
メソッドに渡します。 - ビルダーで
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
を使用してサーバーをビルドして起動します。
メインメソッドでは、次の処理を行います。
RouteGuideServer
インスタンスを作成します。start()
を呼び出して、サービスの RPC サーバーを有効にします。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. ぜひお試しください。
start_here
ディレクトリ内で、次のコマンドを実行します。
$ ./gradlew installDist
これにより、コードがコンパイルされ、jar にパッケージ化され、例を実行するスクリプトが作成されます。これらは build/install/start_here/bin/
ディレクトリに作成されます。スクリプトは route-guide-server
と route-guide-client
です。
クライアントを起動する前に、サーバーが実行されている必要があります。
- サーバーを実行します。
$ ./build/install/start_here/bin/route-guide-server
- クライアントを実行します。
$ ./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. 次のステップ
- gRPC の概要とコアコンセプトで gRPC の仕組みを確認する
- 基本チュートリアルに取り組む
- API リファレンスをご覧ください。