protobuf の Kotlin support
Kotlin で protobuf や gRPC を使うときに、基本的には Java でコンパイルされた protoc の出力を使って開発している方も多いかと思います。
しかし、protobuf には 2021 年ごろ公式に Kotlin support が追加されており、Kotlin DSL 形式で protobuf を使う事が可能になっています。
Kotlin の上で protobuf を利用しているなら、かなり使い心地が違うので、これからは DSL 記法を使っていきたい所です。Kotlin と Java を同時に出力することも可能なので、共存や段階的な移行も可能です。
簡単に、Kotlin support を使うための設定や、便利な点を紹介したいと思います。
Java で出力したクラスを使って実装した場合
従来通り Java で出力した proto は、以下のように builder を使う必要があります。
message HelloRequest {
string name = 1;
}
val request = HelloRequest.newBuilder()
.setName("hktechno")
.build()
Kotlin support を使って DSL 形式で実装した場合
Kotlin support を使うと、以下のようにシンプルな DSL で Kotlin らしく書く事ができます。
val request = helloRequest {
name = "hktechno"
}
その他便利な機能
Kotlin support を使うと Kotlin の nullable との親和性が向上しています。
message フィールドに対して、orNull 付きのメソッドが追加され、nullable として取得する事ができます。optional field と一緒に使うと便利でしょう。
message NullableExample {
optional string message = 1;
optional HelloRequest request = 2;
}
val example = nullableExample { .... }
// message の場合は optional に関わらず getValueOrNull メソッドが提供される
example.requestOrNull?.also {
println("from: ${it.name}")
}
// string など non-message field は従来通り has メソッドを利用 (optional field のみ)
// example.messageOrNull とはできない
if (example.hasMessage()) {
println("message: ${example.message}")
}
gRPC の Kotlin support
gRPC でも Kotlin support が追加されています。
重要な点は coroutines への対応です。CoroutineImplBase
や CoroutineStub
が生成され、それぞれメソッドが suspend fun
となり、gRPC を簡単に Kotlin coroutines へ対応させる事ができます。
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
// Server
class HelloWorldService : GreeterCoroutineImplBase() {
override suspend fun sayHello(request: HelloRequest) = helloReply {
message = "hello, ${request.name}"
}
}
// Client
val stub = GreeterCoroutineStub(channel)
suspend fun hello() {
val request = helloRequest { name = "world" }
val response = stub.sayHello(request)
println("Received: ${response.message}")
}
Gradle ビルド時の設定方法
ここでは、Gradle を利用して、Kotlin support を使って proto をコンパイルするためのビルドスクリプトの設定例を紹介します。
以下の例では、gRPC も使う前提で plugin の設定をしています。
protoc を使う場合
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:<version>"
}
plugins {
id("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:<version>"
}
id("grpckt") {
artifact = "io.grpc:protoc-gen-grpc-kotlin:<version>"
}
}
generateProtoTasks {
all().forEach {
it.builtins {
id("kotlin")
}
}
ofSourceSet("main").forEach {
it.plugins {
id("grpc")
id("grpckt")
}
}
}
}
buf を使う場合の設定
buf.gen.yaml
に以下の様に Kotlin plugin の設定を追加して buf generate すれば問題ありません。
version: v1
plugins:
- plugin: buf.build/grpc/java
out: java
- plugin: buf.build/grpc/kotlin
out: kotlin
- plugin: buf.build/protocolbuffers/java
out: java
- plugin: buf.build/protocolbuffers/kotlin
out: kotlin
Gradle を利用している場合には source set の設定で、以下の様に java と kotlin 両方を含める様にすれば良いでしょう。
sourceSets {
listOf("java", "kotlin").forEach {
val dir = "${layout.buildDirectory.get()}/$BUF_BUILD_DIR/$GENERATED_DIR/$it"
getByName("main").java.srcDir(dir)
}
}