跳到主要内容
版本:dev

Kotlin gRPC 支持

Fory IDL 可以生成 Kotlin 协程 gRPC 配套代码。生成的 gRPC 文件使用标准的 grpc-java 和 grpc-kotlin API,而每条请求和响应消息均由 Fory 序列化。

当 RPC 两端均由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且双方都预期消息体采用 Fory 编码时,请使用此模式。如果 API 必须供通用 protobuf 客户端、反射工具或预期接收 protobuf 消息字节的组件使用,请采用常规的 protobuf gRPC 代码生成方式。

添加依赖

请将 Fory Kotlin、KSP、grpc-java、grpc-kotlin、协程库以及一种 grpc-java 传输实现添加到编译生成源码的应用或服务模块中。

plugins {
id("com.google.devtools.ksp") version "<ksp-version>"
}

dependencies {
implementation("org.apache.fory:fory-kotlin:<fory-version>")
ksp("org.apache.fory:fory-kotlin-ksp:<fory-version>")

implementation("io.grpc:grpc-api:<grpc-version>")
implementation("io.grpc:grpc-stub:<grpc-version>")
implementation("io.grpc:grpc-kotlin-stub:<grpc-kotlin-version>")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:<coroutines-version>")

runtimeOnly("io.grpc:grpc-netty-shaded:<grpc-version>")
}

如果应用已统一采用其他 grpc-java 传输实现,也可以使用该实现。生成的 Kotlin Fory gRPC 不需要通过 grpc-protobuf 编码载荷。

定义服务

服务定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers 的 rpc_service 定义。Fory IDL 服务示例如下:

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

使用 --grpc 生成 Kotlin 模型和 gRPC 配套代码:

foryc service.fdl --kotlin_out=./generated/kotlin --grpc

对于此 schema,Kotlin 生成器会生成:

文件用途
HelloRequest.kt请求的 Fory 模型类型
HelloReply.kt响应的 Fory 模型类型
ServiceForyModule生成类型的 Fory 注册模块
GreeterGrpcKt.kt协程服务基类、存根和编解码器

编译生成的模型文件时,请运行 KSP,以确保 schema 序列化器在运行时可用。生成的请求和响应类型由服务配套代码所使用的生成 schema 模块注册,因此服务实现无需执行自定义序列化器注册。

实现服务端

实现生成的协程基类,并将其注册到标准 grpc-java 服务端。

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloReply
import demo.greeter.HelloRequest
import io.grpc.ServerBuilder

class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
override suspend fun sayHello(request: HelloRequest): HelloReply =
HelloReply(reply = "Hello, ${request.name}")
}

val server = ServerBuilder
.forPort(50051)
.addService(GreeterService())
.build()
.start()

未实现的生成方法会以 gRPC UNIMPLEMENTED 状态失败。服务方法抛出的异常遵循 grpc-kotlin 服务端行为。

创建客户端

直接使用 grpc-java 通道构造生成的协程存根。

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloRequest
import io.grpc.ManagedChannelBuilder

val channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext()
.build()

val stub = GreeterGrpcKt.GreeterCoroutineStub(channel)
val reply = stub.sayHello(HelloRequest(name = "Fory"))

通道的构造和关闭、截止时间、凭据、拦截器、负载均衡、重试以及服务端生命周期,仍由 grpc-java/grpc-kotlin 照常负责。

流式 RPC

Fory 服务定义可以使用相同的 gRPC 流式调用形式:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

流式 RPC 使用 kotlinx.coroutines.flow.Flow

IDL 形式服务端方法客户端方法
rpc A (Req) returns (Res)suspend fun a(request: Req): Ressuspend fun a(request: Req): Res
rpc A (Req) returns (stream Res)fun a(request: Req): Flow<Res>fun a(request: Req): Flow<Res>
rpc A (stream Req) returns (Res)suspend fun a(requests: Flow<Req>): Ressuspend fun a(requests: Flow<Req>): Res
rpc A (stream Req) returns (stream Res)fun a(requests: Flow<Req>): Flow<Res>fun a(requests: Flow<Req>): Flow<Res>

生成的方法路径会保留 schema 中服务和方法名称的准确形式,例如 /demo.greeter.Greeter/SayHello

服务端实现可以直接返回或使用 Flow 值:

import demo.greeter.GreeterGrpcKt
import demo.greeter.HelloReply
import demo.greeter.HelloRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList

class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
emit(HelloReply(reply = "Hello, ${request.name}"))
emit(HelloReply(reply = "Welcome, ${request.name}"))
}

override suspend fun lotsOfGreetings(
requests: Flow<HelloRequest>
): HelloReply {
val names = requests.toList().joinToString(", ") { it.name }
return HelloReply(reply = names)
}

override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
requests.map { request ->
HelloReply(reply = "Hello, ${request.name}")
}
}

生成的客户端会公开匹配的协程和 Flow API:

import demo.greeter.HelloRequest
import kotlinx.coroutines.flow.flowOf

stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
println(reply.reply)
}

val summary = stub.lotsOfGreetings(
flowOf(
HelloRequest(name = "Ada"),
HelloRequest(name = "Grace"),
)
)
println(summary.reply)

stub.chat(
flowOf(
HelloRequest(name = "Fory"),
HelloRequest(name = "RPC"),
)
).collect { reply ->
println(reply.reply)
}

gRPC 运行时行为

生成的服务代码只替换请求和响应的序列化方式。标准 gRPC 的所有运行能力仍由 grpc-java 和 grpc-kotlin 负责:

  • 截止时间和取消
  • TLS 和身份认证
  • 名称解析和负载均衡
  • 客户端和服务端拦截器
  • 状态码和元数据
  • 通道池化和生命周期管理

互操作性

生成的 Kotlin 服务配套代码在 gRPC 帧中使用 Fory 二进制载荷。它们可与根据同一 Schema 生成的其他 Fory gRPC 配套代码互操作,例如 Java、Go、Python 和 Rust 配套代码。通用 protobuf gRPC 客户端无法解码这些载荷。

Fory IDL 服务支持直接使用联合类型作为请求和响应类型。对于 protobuf 输入,请使用 protobuf 前端所接受的 protobuf 服务形式;消息中的 protobuf oneof 字段会转换为 Fory 联合类型字段。

故障排查

未生成服务文件

同时传入 --grpc--kotlin_out。不含服务定义的 schema 只会生成模型文件和 schema 模块。

运行时找不到序列化器类

确保 KSP 会处理生成的 Kotlin 模型源码,并且 fory-kotlin-kspfory-kotlin 使用相同的 Fory 版本。

无法解析 gRPC 类

请将 grpc-java 和 grpc-kotlin 依赖添加到应用模块。Fory Kotlin 构件不会自动添加这些依赖。

Protobuf 客户端无法读取响应

Fory gRPC 使用 Fory 二进制协议载荷,而不是 protobuf 编码格式消息。同一服务 Schema 的两端都应使用生成的 Fory gRPC 配套代码。