Spring boot で gRPC の server reflection を行う

# 目的

Spring Boot + kotlin + gRPC でアプリケーションを実装する上で簡易な動作確認ができるようにgrpcurlというツールを使っています．
このツールはプロトコルバッファの記述子ファイルを必要とするため定義ファイルからそれを作成し使用しています．

しかしこれですとプロトコルバッファの定義ファイルが変更されるたび記述子ファイルの更新もしなければならず面倒なためserver reflectionを使い楽をすることにしました．

grpcurlについてはgithubを参照してください.
https://github.com/fullstorydev/grpcurl

# server reflectionとは

[この記事](https://budougumi0617.github.io/2018/01/04/server-reflection-tutorial/) を参考にさせていただきました．

> サーバーリフレクションはクライアントのコマンドラインからgRPCの定義の取得あるいは実行を可能にするgRPCサーバの拡張機能。サーバーリフレクションによってcurlライクなことがgRPCサーバでも実現できる。

このことからサーバ側からgRPCの定義の提供を受けることで grpcurlが必要としているプロトコルバッファの記述子ファイルがなくても通信ができるようになるようです．

# 環境

- Spring Boot 2.0.0
- grpc-spring-boot-starter 2.2.0
- kotlin 1.2.20
- gradle 4.5.1

## 実装

https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md を参考にします．

### build.gradle

必要なパッケージを追加します.

```diff
 dependencies {
     compile "io.grpc:grpc-netty:${grpcVersion}"
     compile "io.grpc:grpc-protobuf:${grpcVersion}"
     compile "io.grpc:grpc-stub:${grpcVersion}"
+    compile "io.grpc:grpc-services:${grpcVersion}"
     compile "org.lognet:grpc-spring-boot-starter:2.2.0"
```

### interceptor

[grpc-spring-boot-starterのドキュメント](https://github.com/LogNet/grpc-spring-boot-starter#43-custom-grpc-server-configuration) を読むと `GRpcServerBuilderConfigurer` を継承したクラスを用意すればいいようです．

```kotlin
package com.yamatomo.sample.grpcInterceptors

import io.grpc.ServerBuilder
import io.grpc.protobuf.services.ProtoReflectionService
import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
import org.springframework.stereotype.Component

@Component
class BuildConfigure: GRpcServerBuilderConfigurer() {
    override fun configure(serverBuilder: ServerBuilder<*>) {
        serverBuilder.addService(ProtoReflectionService.newInstance())
    }
}
```

あとは gradle bootRun で起動すればOKです. 記述子ファイルがなくとも grpcurl でレスポンスがうけとれるようになります．

```bash
$ grpcurl -plaintext localhost:6565 sample.ExampleService.GetTasks
{
  "task": [
    {
      "id": 1,
      "text": "hogehoge"
    },
    {
      "id": 2,
      "text": "foobar"
    }
  ]
}
```

## 開発環境のみ server reflection が有効になるようにする

あくまで動作確認が目的なので `application.properties` に server reflection の On/Off を定義し開発環境のみ有効にするコードにしてみます.

### application.properties

```properties
grpc.interceptor.useReflection=false
```

### application-development.properties

開発環境用の設定ファイルを用意します．

```properties
grpc.interceptor.useReflection=true
```

### データクラス

`application.properties` をマッピングするデータクラスを作成します．

```kotlin
package com.yamatomo.sample.grpcInterceptors

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties(prefix = "grpc.interceptor")
data class InterceptorProperties (
    var useReflection: Boolean = false
)
```

### interceptor

先ほど実装したコードにデータクラスから On/Off を判定するように書き換えます.

```diff
 package com.yamatomo.sample.grpcInterceptors

 import io.grpc.ServerBuilder
 import io.grpc.protobuf.services.ProtoReflectionService
 import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
+import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Component

 @Component
 class BuildConfigure: GRpcServerBuilderConfigurer() {
+    @Autowired
+    lateinit var properties: InterceptorProperties

     override fun configure(serverBuilder: ServerBuilder<*>) {
+       if (!properties.useReflection) return

        serverBuilder.addService(ProtoReflectionService.newInstance())
     }
 }
```

bootRunのオプションでprofileをdevelopmentと指定して起動します.

```bash
$ gradlew bootRun -Dspring.profiles.active=development
```

# まとめ

気軽に動作確認ができるのは大きいと思いました．

要件によっては `grpc-gateway` を導入しないケースでも動作確認のためだけに導入するといったことも減らせるのではないかなと思います．