技术解读
oRPC 深度解析:当类型安全不再意味着生态绑架
项目背景与痛点
TypeScript 全栈开发里有一个长期未解的矛盾。tRPC 用泛型把「前后端类型一致」做到了极致——调用远程方法像调用本地函数,重构时编译器替你兜底。代价同样明显:tRPC 客户端本质上是一段 TypeScript 代码,非 TS 用户(移动端、Go/Python 服务、第三方合作伙伴)无从消费;它也没有 OpenAPI 文档,API 网关、SDK 生成器、Postman 全都插不上手。
另一端是 REST/OpenAPI。接口天然跨语言,工具链成熟,却要靠 codegen 才能获得类型。生成的类型与真实实现之间隔着一层代码,改一处 schema 忘了重跑生成器,类型和运行时悄悄错位,这就是很多人对 codegen 的疲惫感。
middleapi/orpc 的切入点是:把契约(contract)定义成唯一真相源,让类型安全从类型推导中自然涌现,同时把 OpenAPI 规范作为副产物输出。换句话说,它试图同时兑现 tRPC 的开发体感和 REST 的互操作能力,而不是让开发者在两者之间二选一。
核心技术揭秘
oRPC 的结构可以概括为「契约 → 处理层 → 双通道出口」。程序(procedure)用 schema 声明输入输出,router 把程序组织成树,然后在同一个 contract 之上挂载 RPCHandler 与 OpenAPIHandler 两种处理器,前者服务于原生客户端,后者负责把结构化调用翻译成符合 OpenAPI 3.1 的 REST 语义。
类型推导的关键在客户端一侧。oRPC 的客户端不是 codegen 出来的对象,而是基于 Proxy 构建的递归代理:访问属性时不断累积路径,最终把 router 的类型结构映射为调用签名。这里的技术难点在于 TypeScript 层面的深度递归映射——它必须处理嵌套 router、可选输入、中间件注入的上下文,同时还要避免类型实例化过深导致编辑器卡顿。这类实现通常要借助条件类型与类型级缓存(对已解析的节点做记忆化),否则一个中等规模的 API 树就足以让 IDE 变慢。
传输层刻意贴着 Web 标准走。请求与响应建立在实际的 Request/Response 对象上,而不是自造框架协议。好处是运行时无关性:Node、Bun、Deno、Cloudflare Workers、边缘函数都能用同一份 handler,只要环境提供 fetch。这也让文件上传这类「tRPC 的老大难」变得自然——基于 FormData 流式处理即可,不必再为二进制单独设计通道。
schema 验证被抽象成适配层,zod、valibot、arktype 等都可以接入。校验发生在 handler 入口,错误以结构化形式抛出,客户端侧可复用同一份定义做前置校验,避免无效请求往返。中间件链则承担鉴权、日志、限流等横切关注点,并能在类型层面把上下文(如当前用户)注入到后续 procedure,这是「类型安全」从数据扩展到执行环境的体现。
功能亮点与差异
- 零 codegen 的双向兼容:这不是「先写 RPC 再导出文档」的补丁方案,而是同一份 contract 直接生成 OpenAPI 规范。契约变更后文档自动跟随,消除了生成器与实现之间的漂移。
- REST 语义可精细控制:程序可映射为不同 HTTP 方法与路径,RPC 风格的「一方法一动作」和资源风格的「路径 + 动词」能在同一个 router 里共存。
- 性能路径可选:在 RPC 通道上可以脱离 HTTP 语义做批处理或长连接式优化;需要通用性时再切回标准 HTTP,不用重建整个服务。
- 生态不锁定:非 TS 客户端通过 OpenAPI 即可接入,这一点是 tRPC 结构性缺失的能力。
应用场景与落地建议
最适合的场景是「TS 前端 + 多方消费」的产品型项目、BFF 层、以及需要在 Web 与边缘运行时同时部署的服务。内部微服务之间用 RPC 通道享受类型红利,对外开放接口走 OpenAPI 通道,一个代码库两套出口,治理成本低。
落地时有几处需要留神:一是 schema 库的选择要一次性定好,中途换库意味着所有 contract 都要改写,虽然适配层抽象得不错,但迁移仍是体力活;二是 OpenAPI 自动生成的文档质量取决于 contract 的元信息(描述、示例、错误码),别指望零配置就产出可交付的文档;三是类型推导的编译开销要实测,大 router 建议按业务域拆分,避免单文件类型膨胀;四是错误契约需显式设计,跨语言的消费方不理解 TS 异常,必须收敛为可枚举的错误码。
综合评价
oRPC 的价值不在「又一个 RPC 框架」,而在于它把类型安全从「TS 生态内的私有红利」重新定义为「契约驱动、可对外导出」的公共资产。Proxy 推导 + 标准 fetch + OpenAPI 副产物这三件事组合起来,指向一个清晰的工程答案:你不必为了互操作性牺牲开发体验,也不必为了类型安全把系统锁死在单一语言里。
短板也真实存在:它仍处在快速迭代阶段,API 稳定性不如已被大量生产验证的 tRPC;自动生成的 OpenAPI 在复杂场景(多态响应、精细的 header 契约)下仍需手工补足;对非 TS 团队,收益主要落在服务端一侧,客户端依旧是标准 HTTP 消费,谈不上魔法。如果你的团队以 TypeScript 为主、又需要对外开放接口,它值得认真评估;如果你只需要内部类型安全且永远不涉及第二语言,tRPC 的成熟度仍是更稳的选择。
MisskeyHQ
评论