gRPC ServerをKotlinで動かす
gRPC ServerをKotlin+SpringBootで動かすサンプルを作った。
gorou-178/grpc-springboot-example - GitHub
簡単に作成できると思っていたが、調べて出てくる情報は古くライブラリの依存関係も変わっており動かなかった。 また、Gradleのprotocを利用するのではなくBuf CLIを利用したかった。そのようなサンプルもほぼなかったため作成した。
Buf CLI
Bufが作ったProtocol Bufferの開発を支援するツール。lintや破壊的変更検知、コード生成もできる。
protocコマンドでのコード生成はわりと難しかった。しかし、Buf CLIは、buf.gen.yml
でコード生成の設定ができ、buf generate
でコード生成できる手軽さがうれしい。
Kotlin Codeの生成
以下buf.gen.yml
設定でKotlin Codeの生成ができた。Kotlin+gRPCは、Javaのコードも必要なのがわかりにくかった。
version: v1
plugins:
- plugin: buf.build/grpc/java
out: src/main/java
- plugin: buf.build/grpc/kotlin
out: src/main/kotlin
- plugin: buf.build/protocolbuffers/java:v25.1
out: src/main/java
- plugin: buf.build/protocolbuffers/kotlin:v25.1
out: src/main/kotlin
もうひとつ厄介だったのが、protocolbuffersプラグインにて破壊的変更があったこと。
Protocol Buffers v26.0 の破壊的変更 - Qiita
gradleのprotobuf-javaのversionと、protocのversionに依存関係があり、protoc v26以降はprotobuf-java v4.26以降を利用する必要がある。 それぞれ最新を利用していたがエラーでうまく動かない。根本原因について調べ切れていないが、protobuf-java v3.25にして、protoc v25系にしたところ問題なく動作した。ここにとてつもなく時間を溶かした。
最新版で動作する環境は、今後調べて動かせるようにしたい。
gRPCの破壊的変更の検知
Protocol Bufferの変更に破壊的な変更がある場合に指摘してくれる。 比較対象は、ローカルのGitやGitリポジトリなど指定可能。常にチェックしておけば安心して作業できそう。
マネージモード
Java(Kotlin)でProtocol Buffer利用する際、パッケージ名などをoptionで指定する必要がある。このときパッケージ名は文字列であるため、完全修飾名のようにすべて記載する必要がある。 そのため、複数のProtocol Bufferファイルを利用している場合、パッケージ名の記述が冗長になる。
Buf CLIのマネージモードを利用すると、java_package_prefix
の設定ができるなど、Protocol Bufferファイルへの依存と記述量を減らせる。割とこれが便利だと思っている。
runnのシナリオテスト導入
runnコマンドでgRPCのテストを書いてみたかったため導入。
シナリオファイルを1から書くのは面倒だが、and-runオプション利用すると、リクエスト・レスポンス内容でシナリオを作成してくれるのでとても便利。
runn new --and-run --grpc-no-tls --out test-runn.yml \
-- grpcurl -d '{"name": "test"}' localhost:9090 GreeterService/SayHello
$ cat test-runn.yml
desc: Generated by `runn new`
runners:
greq: grpc://localhost:9090
steps:
- greq:
GreeterService/SayHello:
message:
name: abc
test: |
current.res.headers["content-type"][0] == "application/grpc"
&& current.res.headers["grpc-accept-encoding"][0] == "gzip"
&& compare(current.res.message, {"message":"Hello abc"})
&& current.res.status == 0
protovalidateの導入
protovalidateというものがあることを、runnコマンドのドキュメントを読んでいて知った。Protocol Bufferにvalidationを書け、gRPC Code Generateされ、gRPC Server側でバリデーション処理も行ってくれるプラグイン。
このプラグインは、BSR(Buf Schema Registry: docker hubのようなもの)に登録されているため、プラグイン名の指定で利用できる。
buf.yml
にdeps
を追加するだけ。
version: v1
breaking:
use:
- FILE
lint:
use:
- DEFAULT
allow_comment_ignores: true
deps:
- buf.build/bufbuild/protovalidate
Protocol Bufferは以下のように記述する。重要なのはimport文。
syntax = "proto3";
option java_package = "com.example.grpc.server.greeter";
option java_outer_classname = "Greeter";
import "buf/validate/validate.proto";
message HelloRequest {
string name = 1 [(buf.validate.field).string = {
pattern: "^[a-zA-Z0-9 !,]+$",
min_len: 4,
max_len: 16,
}];
}
このままbuf generate
するとimport "buf/validate/validate.proto";
でエラーになってしまう。
deps
を追加した場合は、buf dep update
を行った上でbuf generate
する必要がある。
protovalidateのgRPC Server側の実装
gradleの場合build.buf:protovalidate
を依存関係に追加の上、以下のように実装することで、Protocol Bufferに記載したバリデーションチェックしてくれる。
Validator().validate()
でisSuccess
がtrue
なら成功とみなす。エラーメッセージは、violations
に配列で返ってくる。
override fun sayHello(request: Greeter.HelloRequest, responseObserver: StreamObserver<Greeter.HelloReply>) {
val reply = Greeter.HelloReply.newBuilder().setMessage("Hello, ${request.name}!").build()
try {
val result = Validator().validate(reply)
if (result.isSuccess) {
responseObserver.onNext(reply)
responseObserver.onCompleted()
} else {
responseObserver.onError(
StatusRuntimeException(
Status.INVALID_ARGUMENT.withDescription(createMessage(result.violations))
)
)
}
} catch (e: Exception) {
responseObserver.onError(
StatusRuntimeException(
createInvalidArgumentStatus(e.message ?: "Unknown error")
)
)
}
}
private fun createMessage(violations: List<Violation>): String {
val sb = StringBuilder()
sb.append("Validation failed:\n")
violations.forEach {
sb.append("property: ").append(it.fieldPath).append("\n")
.append(" message: ").append(it.message).append("\n")
}
return sb.toString()
}
バリデーションルール含めて、runnでシナリオテストの作成ができれば変更時の安心感が高まりそう。
まとめ
Kotlin+SpringBootにてgRPC Serverのサンプルを作成した。 今までは、protocを使ってたがBuf CLIという便利なツールが出てきたため、Buf CLIを利用するフローを模索してみた。
Protocol Bufferベースで、シナリオテストが行えるrunnコマンドとBuf CLIを導入することで、
- Buf CLIのlintによるフォーマットチェック
- Buf CLIの破壊的変更チェック
- Buf CLIでコード生成
- runnのシナリオテスト
これらのチェックが簡単に行える。個人的には満足。 今後、CIに組み込むところをやってみようと思う。