KotlinでSpringBootでgRPCサーバーを立てた時の設定メモ

# 概要
趣味で作るアプリでgRPCを使った通信をしてみようと思い設定した時のメモです。
build.gradleで何を設定する必要があるかについて主に書いています。

Kotlin、SpringBoot、gRPCについては、細かく説明しません。
[こちら](https://github.com/a2kaido/kotlin-springboot-grpc-sample/blob/master/build.gradle)の設定についてのメモです。

# 技術選択の気持ち

## なぜKotlin?
Java8のOptionalは手に馴染まない。isPresentとか書くの面倒くさい。
最近周りで流行している。
かわいい。

サイバーエージェントでは、Androidだけでなく、サーバーサイドでもKotlin使っているところがある。(※1　関連資料)

## なぜSpringBoot?
Javaでアプリケーション書く時のデファクトスタンダード(だと思ってる)。

## なぜgRPC?
触ってみたいから。
趣味で作るアプリなので使ったことがないものを使いたい。

メルカリのバックエンドでも利用されていて(※2　関連資料)、今後マイクロサービス化とともに広まっている可能性がある。

# build.gradleの設定

## buildscript

```groovy
buildscript {
    ext.kotlin_version = '1.1.2'

    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // -- (1)
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.3.RELEASE"
        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.1" // -- (2)
    }
}
```

### (1) kotlin-allopen
KotlinでSpringBootを使うために入れています。Kotlinでは、デフォルトで`final class`になるため、SpringFrameworkの`@Service`などのアノテーションを使った`Autowired`ができません。

`kotlin-allopen`を使うことで、特定のアノテーションが付いているクラスをopenにすることができます。

また、`apply plugin: 'kotlin-spring'`によって、SpringFrameworkで使われるアノテーションがついているクラスをopenにしてくれます。

参考: 
[Compiler Plugins - Kotlin Programming Language] (https://kotlinlang.org/docs/reference/compiler-plugins.html)

### (2) protobuf-gradle-plugin
Protocol Buffersのビルドをするためのプラグイン。

## apply plugin
先ほど出てきましたが、SpringFrameworkを使うため`kotlin-spring`を入れておきます。

```groovy
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'
apply plugin: 'com.google.protobuf'
apply plugin: 'application'
```

## repositories
`grpc-spring-boot-starter`が`jcenter()`にあるので追加しています。

```groovy 
repositories {
    mavenCentral()
    jcenter()
}
```

## sourceSets
Protocol Buffersが生成するソースのディレクトリを追加しておきます。

```groovy
sourceSets {
    main.kotlin.srcDirs += 'src/main/kotlin'
    main.java.srcDirs += 'src/main/java'
    main.java.srcDirs += 'src/main/generated-proto'
}
```

## dependencies
```groovy
def grpcVersion = '1.5.0'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

    // grpc
    compile "io.grpc:grpc-netty:${grpcVersion}" // -- (1)
    compile "io.grpc:grpc-protobuf:${grpcVersion}" // -- (2)
    compile "io.grpc:grpc-stub:${grpcVersion}" // -- (3)

    // spring-boot
    compile "org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE"
    compile "org.springframework.boot:spring-boot-actuator:1.5.3.RELEASE"
    compile "org.lognet:grpc-spring-boot-starter:2.0.4" // -- (4)
}
```
### (1) grpc-netty
書かなくても`org.lognet:grpc-spring-boot-starter`の依存で入ります。
ただ、バージョン1.4.0が入って通信に失敗するので、1.5.0を明示的に書いてます。

### (2) grpc-protobuf
Protocol Buffersが生成したクラスの中で使っているので必須。

### (3) grpc-stub
Protocol Buffersが生成したクラスの中で使っているので必須。

### (4) [grpc-spring-boot-starter](https://github.com/LogNet/grpc-spring-boot-starter)
`@GRpcService`をつけるだけで、SpringBootがgRPCのサービスとして起動してくれるようになるOSS。
Eurekaも一緒に使えるようなので、ロードバランシングもできそう。(ｷﾀｺﾚ!)

Apache License 2.0。

## protobuf
Protocol Buffersのコンパイル設定を書きます。

参考: [protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin)

```groovy
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.3.0' // -- (1)
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:1.5.0" // -- (2)
        }
    }
    generateProtoTasks { // -- (3)
        all().each { task ->
            task.builtins {
                java {
                    outputSubDir = 'generated-proto'
                }
            }
            task.plugins {
                grpc {
                    outputSubDir = 'generated-proto'
                }
            }
        }
    }
    generatedFilesBaseDir = "$projectDir/src/" // -- (4)
}
```

### (1) protoc
Protocol Buffersのコンパイラを指定。
前述した、grpc-protobufやgrpc-stubのバージョンとprotocのバージョンには依存関係があり、バージョンがズレていると、Protocol Buffersが生成したjavaのコンパイルに失敗することがあります。(依存関係があるのに依存関係チェックしてくれない。。)

どちらも最新版にしておけば、動くと思います。

### (2) protoc-gen-grpc-java
protocでJavaを生成するためにPluginを入れます。
Protocol Buffersは、GoやPHPなどの様々な言語への出力ができるようになっています。残念ながらまだKotlinを生成できないので、Javaにしておきます。

### (3) generateProtoTasks
生成したJavaファイルの配置先を設定します。
`task.builtins`のファイルは、デフォルトで`"${generatedFilesBaseDir}/main/java"`の下に置かれるようです。
`task.plugins`のファイルは、設定を入れないとどこにも出力されませんでした。

ここでは、Protocol Buffersが出力したことが分かりやすいように、`generated-proto`としています。

### (4) generatedFilesBaseDir
生成したJavaファイルの配置先を設定します。
(3)で設定したものと合わせて、`"${generatedFilesBaseDir}/main/${outputSubDir}"`に生成されます。

## clean
gradle clean時に生成ファイルが消えるように設定しておきます。

```groovy
clean {
    delete "$projectDir/src/main/generated-proto"
}
```

## bootRepackage
fat jarを作るために`bootRepackage`いれておきます。

```groovy
bootRepackage  {
    executable = true
}
```

## build.gradle設定まとめ
build.gradleの設定はここまでです。ソースは[こちら](https://github.com/a2kaido/kotlin-springboot-grpc-sample/blob/master/build.gradle)。

# その他つらつらと

## .gitignore
Protocol Buffersのコンパイルで生成されるファイルがgitに入らないようにignoreしましょう。

```
generated-proto
```

## Dockerfile
javaが入っている環境で作ったfat jarを実行するだけのDockerイメージを作ると、runするだけでサーバー上で動かせます。
ありがとうDocker。

```dockerfile:Dockerfile
FROM java:8-jre

ADD sample-1.0.0.jar /app/

CMD ["java", "-jar", "/app/sample-1.0.0.jar"]

EXPOSE 6565
```

## サーバーの動作確認
[polyglot](https://github.com/grpc-ecosystem/polyglot)を利用すると簡単な動作確認ができます。
protoファイル、通信先、実行するメソッドを指定すると、通信結果をjsonで吐き出してくれます。

```bash:コマンド
echo "{'name': 'world'}" | java -jar ~/polyglot.jar \
--command=call --endpoint=localhost:6565 \
--full_method=helloworld.Greeter/SayHello \
--proto_discovery_root=src/main/proto/ \
--use_tls=false
```

```bash:実行結果
[main] INFO me.dinowernli.grpc.polyglot.Main - Polyglot version: 1.2.0
[main] INFO me.dinowernli.grpc.polyglot.Main - Loaded configuration: 
[main] INFO me.dinowernli.grpc.polyglot.command.ServiceCall - Creating dynamic grpc client
[main] INFO io.grpc.internal.ManagedChannelImpl - [ManagedChannelImpl@25af5db5] Created with target localhost:6565
[main] INFO me.dinowernli.grpc.polyglot.command.ServiceCall - Making rpc with 1 request(s) to endpoint [localhost:6565]
[main] INFO me.dinowernli.grpc.polyglot.grpc.DynamicGrpcClient - Making unary call
[grpc-default-executor-1] INFO me.dinowernli.grpc.polyglot.io.LoggingStatsWriter - Got response message
{
  "message": "Hello world"
}

[grpc-default-executor-1] INFO me.dinowernli.grpc.polyglot.io.LoggingStatsWriter - Completed rpc with 1 response(s)

```
curlの方が簡単だけど、gRPCだとcurlできないので仕方ない。

## KotlinでのSpringFrameworkのコンストラクタインジェクションの書き方
```kotlin:kotlin
@Service
class SampleService @Autowired constructor(val repository: SampleRepository) {
}
```

Javaにデコンパイルすると以下のようになって、確かに動きそう。(Kotlinなのでgetterが勝手に生えるw)

```java:Java.decompiled
@Service
public class SampleService {
   @NotNull
   private final SampleRepository repository;

   @NotNull
   public SampleRepository getRepository() {
      return this.repository;
   }

   @Autowired
   public SampleService(@NotNull SampleRepository repository) {
      Intrinsics.checkParameterIsNotNull(repository, "repository");
      super();
      this.repository = repository;
   }
}
```

# 関連資料
※1 [GRPCの実践と現状での利点欠点 / Go Conference 2016 Spring](http://sssslide.com/speakerdeck.com/kazegusuri/go-conference-2016-spring)
※2 [サイバーエージェントのKotlin勉強会「CA.kt」がスタートーーベータ版から開発で利用している主催者に話を聞きました](https://developers.cyberagent.co.jp/blog/archives/7412/)