概要
趣味で作るアプリでgRPCを使った通信をしてみようと思い設定した時のメモです。
build.gradleで何を設定する必要があるかについて主に書いています。
Kotlin、SpringBoot、gRPCについては、細かく説明しません。
こちらの設定についてのメモです。
技術選択の気持ち
なぜKotlin?
Java8のOptionalは手に馴染まない。isPresentとか書くの面倒くさい。
最近周りで流行している。
かわいい。
サイバーエージェントでは、Androidだけでなく、サーバーサイドでもKotlin使っているところがある。(※1 関連資料)
なぜSpringBoot?
Javaでアプリケーション書く時のデファクトスタンダード(だと思ってる)。
なぜgRPC?
触ってみたいから。
趣味で作るアプリなので使ったことがないものを使いたい。
メルカリのバックエンドでも利用されていて(※2 関連資料)、今後マイクロサービス化とともに広まっている可能性がある。
build.gradleの設定
buildscript
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
を入れておきます。
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()
にあるので追加しています。
repositories {
mavenCentral()
jcenter()
}
sourceSets
Protocol Buffersが生成するソースのディレクトリを追加しておきます。
sourceSets {
main.kotlin.srcDirs += 'src/main/kotlin'
main.java.srcDirs += 'src/main/java'
main.java.srcDirs += 'src/main/generated-proto'
}
dependencies
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
@GRpcService
をつけるだけで、SpringBootがgRPCのサービスとして起動してくれるようになるOSS。
Eurekaも一緒に使えるようなので、ロードバランシングもできそう。(キタコレ!)
Apache License 2.0。
protobuf
Protocol Buffersのコンパイル設定を書きます。
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時に生成ファイルが消えるように設定しておきます。
clean {
delete "$projectDir/src/main/generated-proto"
}
bootRepackage
fat jarを作るためにbootRepackage
いれておきます。
bootRepackage {
executable = true
}
build.gradle設定まとめ
build.gradleの設定はここまでです。ソースはこちら。
その他つらつらと
.gitignore
Protocol Buffersのコンパイルで生成されるファイルがgitに入らないようにignoreしましょう。
generated-proto
Dockerfile
javaが入っている環境で作ったfat jarを実行するだけのDockerイメージを作ると、runするだけでサーバー上で動かせます。
ありがとうDocker。
FROM java:8-jre
ADD sample-1.0.0.jar /app/
CMD ["java", "-jar", "/app/sample-1.0.0.jar"]
EXPOSE 6565
サーバーの動作確認
polyglotを利用すると簡単な動作確認ができます。
protoファイル、通信先、実行するメソッドを指定すると、通信結果をjsonで吐き出してくれます。
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
[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のコンストラクタインジェクションの書き方
@Service
class SampleService @Autowired constructor(val repository: SampleRepository) {
}
Javaにデコンパイルすると以下のようになって、確かに動きそう。(Kotlinなのでgetterが勝手に生えるw)
@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
※2 サイバーエージェントのKotlin勉強会「CA.kt」がスタートーーベータ版から開発で利用している主催者に話を聞きました