3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

protobuf や gRPC を Kotlin DSL で実装してみる

Last updated at Posted at 2024-03-08

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 を使う必要があります。

hello.proto
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 と一緒に使うと便利でしょう。

optional.proto
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 への対応です。CoroutineImplBaseCoroutineStub が生成され、それぞれメソッドが suspend fun となり、gRPC を簡単に Kotlin coroutines へ対応させる事ができます。

hello.proto
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 を使う場合

build.gradle.kts
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 すれば問題ありません。

buf.gen.yaml
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 両方を含める様にすれば良いでしょう。

build.gradle.kts
sourceSets {
    listOf("java", "kotlin").forEach {
        val dir = "${layout.buildDirectory.get()}/$BUF_BUILD_DIR/$GENERATED_DIR/$it"
        getByName("main").java.srcDir(dir)
    }
}

参考文献

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?