AndroidでgRPC通信を行う

# はじめに
AbemaTV Advent Calendar 2017 12日目の記事です。
最近、個人的にgRPCについて調査する機会があったので、自分への勉強の意味も込めて記事にしたいと思います。

ちなみに、この内容は[こちらのLT](https://speakerdeck.com/takusemba/droidconsf-report)で発表した物になります。

# gRPCとは
gRPCとはRemoteProcedureCallの一つで、ClientとServer間でProtocolBuffersを使って通信します。
HTTP/2を使用するため、双方向ストリーミング、フロー制御、ヘッダー圧縮、多重リクエストなどHTTP/2の特徴を活かすことができます。
また、Protoファイルと呼ばれる通信をする上でのMessage(型)やService(メソッド)の定義ファイルを、Server側とClinet側で共通して使うことになるため、お互いが共通の認識を持って通信を行うことができます。

公式サイトは[こちら](https://grpc.io)

# 手順
1. Protoファイルの定義
2. Protoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成する
3. AndroidでgRPC通信を行う

# Protoファイルの定義
まずはProtoファイルの定義を行います。
Protoファイルは、ClinentとServerがどのように通信をするのかを定義するためのファイルです。

まずはMessageと呼ばれるProtocolBuffersで通信をする上での型を定義します。

↓はリクエスト時とレスポンス時に使用する型をProtoファイルに定義しています。
StringのNameを持ったCoffeeRequestという型でRequestを送り、レスポンスとして、price, name, messageを持ったCoffeeResponseとして受け取っています。
プロパティに割り振られている数字はタグのようなものになります。ユニークなnumberを割り当てます。

```proto

message CoffeeRequest {
    string name = 1;
}


message CoffeeResponse {
    int32 price = 1;
    string name = 2;
    string message = 3;
}

```


次にgRPC通信を行うために必要なServiceというものを定義します。ここに定義したServiceを通してClientとServerがやりとりします。

ここでは、OrderというServiceを定義しました。
Orderの引数には先程の`CoffeeRequest`を指定し、レスポンスとして、`CoffeeResponse`を受け取ります。

```proto

service Coffee {
    rpc Order (CoffeeRequest) returns (CoffeeResponse) {}
}
```

全体で見ると↓のようになると思います。

```proto

syntax = "proto3";

option java_package = "com.takusemba.grpc.android.protos";
option go_package = "protos";

package Coffee;

message CoffeeRequest {
    string name = 1;
}

message CoffeeResult {
    string message = 1;
}

service Coffee {
    rpc Order (CoffeeRequest) returns (CoffeeResult) {}
}
```

## Serviceの種類
ここでServiceの種類について紹介します。

### Unary RPCs

```proto

service Coffee {
    rpc Order (CoffeeRequest) returns (CoffeeResponse) {}
}
```

先程定義したServiceは`Unary RPCs`と呼ばれるServiceでClientが一つリクエストを送るとServer側が一つレスポンスを返す基本的な形です。

### Server streaming RPCs その１

```proto

service Coffee {
    rpc Order (stream CoffeeRequest) returns (CoffeeResponse) {}
}
```

Orderの引数にstreamというものがつきました。これは、`Server streaming RPCs`と呼ばれるもので、Clientが複数のリクエストを送ることができ、Server側は全てのリクエストを受け取り、一つのレスポンスを返すことができます。

### Server streaming RPCs その2

```proto

service Coffee {
    rpc Order (CoffeeRequest) returns (stream CoffeeResponse) {}
}
```

また、レスポンス側にもstreamをつけることができます。 この場合は、クライアントが一つのリクエストを送るのに対して、server側は複数のレスポンスを送ることができます。このサーバーが複数のレスポンスを返却するような形を利用すると、サーバープッシュのような、あるイベントをclientが購読するような仕組みを作ることができます。

### Bidirectional streaming RPCs

```proto

service Coffee {
    rpc Order (stream CoffeeRequest) returns (stream CoffeeResponse) {}
}
```

また、リクエスト側とレスポンス側の両方にもstreamをつけることができます。これは、`Bidirectional streaming RPCs`と呼ばれています。server側はclientが全てリクエストを送りきったあとにレスポンスを送ることもできますし、severとclientが交互にメッセージを送り合うこともできます。


# Protoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成する

今度は、先程定義したProtoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成します。この生成されたファイルを使用して、ClientとServerが通信することになります。

### protocコマンド
protocコマンドとは、ProtocolBuffers通信を行う部分のファイルを生成してくれます。
Protoファイルで言うとMessageの部分です。
まずは、protocコマンドが使える状態にします。

```shell
curl -OL https://github.com/google/protobuf/releases/download/v3.5.0/protobuf-java-3.5.0.tar.gz
tar -zxvf protobuf-java-3.5.0.tar.gz
cd checkout -b protobuf-3.5.0 origin/protobuf-3.5.0
./configure
make
sudo make install
protoc —version // -> libprotoc 3.5.0 :)
```
### protoc-gen-grpc-javaプラグイン

Protoファイルで定義したService層のファイルを生成するためにはこの`protoc-gen-grpc-java`プラグインが必要になります。

```shell
git clone git@github.com:grpc/grpc-java.git
cd grpc-java
git checkout -b v1.7.0
cd compiler
make
../gradlew java_pluginExecutable
cp build/exe/java_plugin/protoc-gen-grpc-java /usr/local/bin
```

### Javaファイルの生成

`protoc`と`protoc-gen-grpc-java`が使えるようになると、以下のコマンドを利用して、Javaファイルを生成することができます。

```
protoc coffee.proto --grpc-java_out=lite:. --java_out=. --plugin=protoc-gen-grpc-java=/usr/local/bin/protoc-gen-grpc-java
```

すると[こののような](https://github.com/TakuSemba/grpc-proto/tree/master/java/com/takusemba/grpc/android/protos)ファイルが生成されていると思います。

# AndroidでgRPC通信を行う

最後にAndroidでgRPC通信を実装していきたいと思います。

まず、build.gradleを編集します。

```build.gradle
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2'
```

```app/build.gradle
implementation "io.grpc:grpc-okhttp:1.7.0"
implementation "io.grpc:grpc-protobuf:1.7.0"
implementation "io.grpc:grpc-stub:1.7.0"
implementation "javax.annotation:javax.annotation-api:1.2"
```

`app/build.gradle`で追加する`io.grpc:grpc-xxx`のバージョンは先程利用した、`protoc-gen-grpc-java`と同じバージョンで揃える必要があります。


次に通信部分の実装について見ていきます。

```kotlin
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 8080)
   .usePlaintext(true)
   .build()

val stub = CoffeeGrpc.newBlockingStub(channel)

val request = CoffeeOuterClass.CoffeeRequest.newBuilder()
   .setName("hot coffee")
   .build()

Single
   .create<CoffeeOuterClass.CoffeeResponse> {
     it.onSuccess(stub.order(request))
   }.subscribeOn(Schedulers.io())
   .observeOn(AndroidSchedulers.mainThread())
   .subscribe(
       { /** do something */ },
       { /**  error handling */ }
   )
```

まず、ipアドレスとportを指定して、`channel`を生成しています。ここでは、エミュレータからlocalhostアクセスするため、portに`10.0.2.2`を指定しています。

Stubには、`non-blocking`または、`blocking`なものなど複数種類があるのですが、ここでは、単純に`blockingStub`を生成しています。
詳しくは[公式のサイト](https://grpc.io/docs/tutorials/basic/java.html#creating-the-client)を御覧ください。

実際にリクエストする部分では、RxのSingleを使って、`order()`を呼び出し、onNextで結果を受け取っています。

# さいごに
今回、gRPCで通信をするまでの流れをざっと書いてきましたが、今回の説明で使った例を使ったサンプルがありますので、よかったら見てみてください。

Clientサイド(java): https://github.com/TakuSemba/grpc-android

Serverサイド(Go): https://github.com/TakuSemba/grpc-go

Protoファイル: https://github.com/TakuSemba/grpc-proto