Effect 的 RPC 契约怎么写?
两个服务之间的接口,最常见的事故是一边悄悄改了字段,另一边直到线上才发现。
在 Effect 里,先写一份两端共享的契约文件:用 Rpc.make 声明每个调用的名字、payload、成功类型和错误类型,再用 RpcGroup.make 把它们归成一组。 服务端据此实现处理函数,客户端据此得到类型完整的调用接口。
两端用的是同一份 Schema,所以请求或响应的任何一方悄悄变了形状,在编译期和解码期都会暴露。RPC 是“跨进程调用”的又一个实例,整体形状见 Effect 里调用 HTTP、数据库和 RPC,怎么写才不踩坑?。下面看契约怎么写,以及业务失败为什么能跨网络保持类型。
一份契约长什么样?
import { Schema } from "effect"
import { Rpc, RpcGroup } from "effect/rpc"
class User extends Schema.Class<User>("User")({
id: Schema.String,
name: Schema.String
}) {}
class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", {
id: Schema.String
}) {}
export const UserRpcs = RpcGroup.make(
Rpc.make("GetUser", {
payload: { id: Schema.String },
success: User,
error: UserNotFound
})
)GetUser 声明了入参 { id }、成功时返回 User、失败时返回 UserNotFound。
业务失败为什么能保持类型?
error 是一个 Schema,所以错误类要用 Schema.TaggedError,这是它真正派上用场的地方。业务失败是契约的一部分,会被编码后跨网络传回,客户端拿到的仍然是类型化的 UserNotFound。
进程内流动的错误用 Data.TaggedError 就够了,区别见 错误设计。
连接断了算哪种错误?
「连接断了」这类传输失败是另外的错误类型,即客户端的 RpcClientError,不会和业务失败混在一起。业务失败和传输失败各占各的位置,调用方可以分别处理。
传输方式怎么选?
传输同样是 Layer:同一份契约,可以挂在 HTTP、WebSocket、Worker 等不同协议之上。换协议是换 Layer,不改契约,也不改业务代码。Layer 的用法见 Layer。
更多 RPC 与 SQL 的数据模型见 Effect 的 SQL 与 RPC。
基于 effect 4.0.5。这里的代码沿用本系列原有的写法,这次没有重新运行;effect/rpc 在该版本中标注为 unstable,小版本之间 API 可能变动,以你使用的版本为准。