今更だけど gRPC を試してみた

## はじめに
本記事は、[アプレッソ Advent Calendar 2016](http://qiita.com/advent-calendar/2016/appresso) の 23 日目の記事です。

今年の 9 月に JavaOne 2016 参加した時に、gRPC のセッションに参加してからずっと気になるので、今回の記事を機に試してみたいと思います。

## 環境準備

* JDK[^jdk]: jdk1.8.0_112
* Git[^git]

## 早速サンプルの Hello World を試してみる

まずは[公式サイトのクイックガイド](http://www.grpc.io/docs/quickstart/java.html)を参考に、動くところまでやってみます。

#### ダウンロード

GitHub から Java の gRPC をゲットします。

```bash
$ # 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](https://github.com/grpc/grpc-java/blob/v1.0.x/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java) 内で使用したユニコード文字（Φ、λ、Δ）のせいでコンパイルでコケる場合があります。

その場合はこの [GitHub の ISSUE](https://github.com/grpc/grpc-java/pull/2416) を参考に、これらの文字を使用しないように中身を変更するか、エンコーディング設定を変更してください。

#### 実行

コンパイルできたら、同じフォルダ内で実行します。

`$ ./build/install/examples/bin/hello-world-server`

![hello_world_server.png](https://qiita-image-store.s3.amazonaws.com/0/30021/c9e48c22-fa55-5ad0-3d66-b99317512e82.png)

同じフォルダ、別のターミナルでクライアントを実行します。

`$ ./build/install/examples/bin/hello-world-client`

![hello_world_client.png](https://qiita-image-store.s3.amazonaws.com/0/30021/57f41599-2cfd-e064-0a1f-3f41eee30ae0.png)

ちなみにサーバを起動せずに、クライアントだけを実行すると例外が発生します。

![hello_world_client_NG.png](https://qiita-image-store.s3.amazonaws.com/0/30021/74756953-e267-cd63-e5be-e1ee779417fc.png)

#### 実行できた

思ったよりずっと簡単に実行できました。
でも何をやっているかはまださっぱり分かりませんね。
続いて中身を覗いてみて、いじってみたいと思います。

## 気になる Hello World の中身

サンプルフォルダに多数のファイルが含まれているが、Hello World で使用するのは以下のファイルだけです。

（examples/src）
![hello_world.PNG](https://qiita-image-store.s3.amazonaws.com/0/30021/948c5932-2d1e-da54-ac93-fa530de5ac2e.png)

#### helloworld.proto

このファイルは gRPC のインタフェースを定義するためのものです。このファイルを元に、generated 以下のものがビルド時に生成されます。

（examples/build/generated）
![generated.PNG](https://qiita-image-store.s3.amazonaws.com/0/30021/db6749e0-8694-c554-a5fd-bb424538a5a4.png)

.proto の書き方は [Protocol Buffers](https://developers.google.com/protocol-buffers/) の言語に準拠していて、文法に関する説明は [Language Guide (proto3)](https://developers.google.com/protocol-buffers/docs/proto3?hl=ja)  のページにあります。

```protobuf
// 元のコメントを省略しました
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

サーバのスタート、停止などの処理以外に、呼び出し対象となるメソッドの実装が入っています。

```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

コンストラクタ内でリモート呼び出し用のスタブを作成します。

```java
// 元のコメントとログ出力を省略しました
public HelloWorldClient(String host, int port) {
    channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build();
    blockingStub = GreeterGrpc.newBlockingStub(channel);
}
```

スタブ経由で実際の処理を呼び出します。

```java
// 元のコメントとログ出力を省略しました
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` を追加します。

```protobuf
service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
    rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
```

#### サーバとクライアントを更新する


サーバ側は `sayHello` をパクって `sayHelloAgain` を作ります。

```java
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` の最後にもう一つ呼び出しを追加します。

```java
// 元のコメントとログ出力を省略しました
public void greet(String name) {
    ...
    try {
        response = blockingStub.sayHelloAgain(request);
    } catch (StatusRuntimeException e) {
        return;
    }
}
```

最初の手順と同じようにコンパイル・実行すると、クライアントの方で以下の出力になりました。

![hello_world_client2.png](https://qiita-image-store.s3.amazonaws.com/0/30021/c7621213-6ada-074d-9744-a1c47ad65d69.png)

## おわりに

gRPC のクイックガイドを一通りやって、中身も少しのぞいてみました。
Protobuf によるインタフェースとメッセージの定義は意外と分かりやすくて、サーバ側とクライアント側の実装も結構直感的な形になっています。
全体にはまだ分からないことだらけですが、交換されるメッセージを gRPC が処理してくれるのであれば、RMI のようなシリアライズとそれに付随する面倒なことを避けられるかもしれません。

今後は複数言語間の連携と、バージョンアップ時の互換性保証、実行パフォーマンスなど、
プロダクト開発において影響の大きい面を深く調べていきたいと思います。

ちなみに、JavaOne 2016 の時に参加したセッションは Google の Ray Tsang さんによる "gRPC 101 for Java Developers" でした。
その時の映像は公開していないようですが、別のイベントのビデオに同じ内容のものを発見したので、興味ある方はぜひ視聴してみてください。

* [gRPC 101 for Java Developers - WEB2DAY 2016](https://www.youtube.com/watch?v=5tmPvSe7xXQ)



[^jdk]: gRPC 1.0.0 の場合、Java の動作環境 JDK 7 以上です。
[^git]: clone するだけなので、バージョン特には気にしなくてよいです。