跳到主要内容
版本:dev

gRPC 支持

Fory 可以为包含 service 定义的 schema 生成 Scala 3 gRPC service companion。生成代码使用普通 grpc-java channel、server、deadline、status code、interceptor 和 transport security,但 request/response 对象使用 Fory 序列化,而不是 protobuf。

当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果 API 必须被通用 protobuf client 或 reflection 工具消费,请使用标准 protobuf gRPC 代码生成。

添加依赖

生成的 Scala service 文件编译时需要 grpc-java。fory-scala artifact 不会把 gRPC 作为硬依赖:

libraryDependencies ++= Seq(
"org.apache.fory" %% "fory-scala" % "<fory-version>",
"io.grpc" % "grpc-api" % "<grpc-version>",
"io.grpc" % "grpc-stub" % "<grpc-version>",
"io.grpc" % "grpc-netty-shaded" % "<grpc-version>"
)

生成的 Scala model 和 gRPC companion 是 Scala 3 source。fory-scala 仍同时 cross-build Scala 2.13 和 Scala 3。

定义 Service

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

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

生成 Scala model 和 gRPC companion:

foryc service.fdl --scala_out=./generated/scala --grpc

输出包含:

文件用途
HelloRequest.scalarequest 的 Fory model type
HelloReply.scalaresponse 的 Fory model type
GreeterForyModule.scala生成类型的 Fory 注册 module
GreeterGrpc.scalagrpc-java service base、client 和 codec

实现 Server

继承生成的 GreeterGrpc.GreeterImplBase,并注册到标准 grpc-java Server

package demo.greeter

import io.grpc.ServerBuilder

final class GreeterService extends GreeterGrpc.GreeterImplBase {
override def sayHello(request: HelloRequest): HelloReply =
HelloReply(s"Hello, ${request.name}")
}

@main def runServer(): Unit = {
val server = ServerBuilder
.forPort(50051)
.addService(new GreeterService)
.build()
.start()
server.awaitTermination()
}

生成代码会注册 request/response 类型,service 实现不需要手动注册 serializer。

创建 Client

使用普通 grpc-java channel 和生成 client:

package demo.greeter

import io.grpc.ManagedChannelBuilder
import scala.concurrent.Await
import scala.concurrent.duration.DurationInt

@main def runClient(): Unit = {
val channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext()
.build()
try {
val client = GreeterGrpc.newClient(channel)
val call = client.sayHello(HelloRequest("Fory"))
val reply = Await.result(call.asFuture, 30.seconds)
println(reply.reply)
} finally {
channel.shutdownNow()
}
}

Unary Scala-friendly 方法返回 RpcFuture[A]。调用者可以通过 asFuture 与 Scala Future 组合, 并在需要提前取消 RPC 时调用 cancel()

Streaming RPC

Fory service 支持与 grpc-java 相同的 streaming shape:

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);
}
IDL shapeScala client conveniencegrpc-java-style 方法
UnaryRpcFuture[Resp]async observer、blocking、ListenableFuture
Server streamingRpcIterator[Resp]async observer 和 blocking iterator
Client streamingStreamObserver request stream
Bidirectional streamingrequest/response StreamObserver

Server-streaming 的 RpcIterator[A] 扩展 Scala Iterator[A]AutoCloseable。如果调用者提前停止消费, 应调用 close()cancel() 释放底层 gRPC call。

Client-streaming 和 bidirectional streaming 保持 grpc-java StreamObserver API,因为 request stream lifecycle、completion、cancellation 和 flow-control 规则都属于 grpc-java。

故障排查

缺少 grpc-java 类

添加 grpc-apigrpc-stub 和一个 transport,例如 grpc-netty-shaded

Protobuf Client 无法读取响应

Fory gRPC 使用 Fory 二进制协议 payload。请在两端使用同一份 schema 生成的 Fory gRPC companion。