はじめに
本記事は、アプレッソ Advent Calendar 2016 の 23 日目の記事です。
今年の 9 月に JavaOne 2016 参加した時に、gRPC のセッションに参加してからずっと気になるので、今回の記事を機に試してみたいと思います。
環境準備
早速サンプルの Hello World を試してみる
まずは公式サイトのクイックガイドを参考に、動くところまでやってみます。
ダウンロード
GitHub から Java の gRPC をゲットします。
$ # 2016/12/23 現在最新リリースは v1.0.3
$ git clone -b v1.0.3 https://github.com/grpc/grpc-java
$ # Java 用のサンプルフォルダに移動
$ cd grpc-java/examples
コンパイル
サンプルフォルダ(grpc-java/examples)内でサーバとクライアントをコンパイルします。
$ ./gradlew installDist
エンコーディングの設定によって RouteGuideServer.java 内で使用したユニコード文字(Φ、λ、Δ)のせいでコンパイルでコケる場合があります。
その場合はこの GitHub の ISSUE を参考に、これらの文字を使用しないように中身を変更するか、エンコーディング設定を変更してください。
実行
コンパイルできたら、同じフォルダ内で実行します。
$ ./build/install/examples/bin/hello-world-server
同じフォルダ、別のターミナルでクライアントを実行します。
$ ./build/install/examples/bin/hello-world-client
ちなみにサーバを起動せずに、クライアントだけを実行すると例外が発生します。
実行できた
思ったよりずっと簡単に実行できました。
でも何をやっているかはまださっぱり分かりませんね。
続いて中身を覗いてみて、いじってみたいと思います。
気になる Hello World の中身
サンプルフォルダに多数のファイルが含まれているが、Hello World で使用するのは以下のファイルだけです。
helloworld.proto
このファイルは gRPC のインタフェースを定義するためのものです。このファイルを元に、generated 以下のものがビルド時に生成されます。
.proto の書き方は Protocol Buffers の言語に準拠していて、文法に関する説明は Language Guide (proto3) のページにあります。
// 元のコメントを省略しました
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
(↑ HelloReply のフィールドに message というキーワードを使っているので、ハイライトがバグりました…)
HelloWorldServer.java
サーバのスタート、停止などの処理以外に、呼び出し対象となるメソッドの実装が入っています。
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
HelloWorldClient.java
コンストラクタ内でリモート呼び出し用のスタブを作成します。
// 元のコメントとログ出力を省略しました
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
スタブ経由で実際の処理を呼び出します。
// 元のコメントとログ出力を省略しました
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
return;
}
}
クイックガイドの続き
中身を見たところ、実に分かりやすい構成でした。
大体把握したが、せっかくなので、クイックガイドの続きを一通りやっていきます。
gRPC サービスを更新する
helloworld.proto の Greeter に新しいメソッド SayHelloAgain
を追加します。
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
サーバとクライアントを更新する
サーバ側は sayHello
をパクって sayHelloAgain
を作ります。
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
クライアントは greet
の最後にもう一つ呼び出しを追加します。
// 元のコメントとログ出力を省略しました
public void greet(String name) {
...
try {
response = blockingStub.sayHelloAgain(request);
} catch (StatusRuntimeException e) {
return;
}
}
最初の手順と同じようにコンパイル・実行すると、クライアントの方で以下の出力になりました。
おわりに
gRPC のクイックガイドを一通りやって、中身も少しのぞいてみました。
Protobuf によるインタフェースとメッセージの定義は意外と分かりやすくて、サーバ側とクライアント側の実装も結構直感的な形になっています。
全体にはまだ分からないことだらけですが、交換されるメッセージを gRPC が処理してくれるのであれば、RMI のようなシリアライズとそれに付随する面倒なことを避けられるかもしれません。
今後は複数言語間の連携と、バージョンアップ時の互換性保証、実行パフォーマンスなど、
プロダクト開発において影響の大きい面を深く調べていきたいと思います。
ちなみに、JavaOne 2016 の時に参加したセッションは Google の Ray Tsang さんによる "gRPC 101 for Java Developers" でした。
その時の映像は公開していないようですが、別のイベントのビデオに同じ内容のものを発見したので、興味ある方はぜひ視聴してみてください。