使用 gRPC 设计 API 的优势
现阶段 API 设计的问题
在开发过程中,有一些很困扰前、后端团队交互的问题:
谁来设计 API?
提供什么形式的 API?
什么时候可以提供 API?
对于第一个问题,通常情况下都是由后端人员来设计 API,这就造成前端人员会在开发初期的一段时间内没法作数据模型和服务端交互方面的工作。这时,一些独立的 API 管理工具就派上用场了,比如:类似 yapi 这样的 API 管理/Mock 工具。前端人员可以不用等后端设计 API,直接在 yapi 上设计接口并可以提供一些“无用”的模拟数据。但是,这又会引入些其它问题,在 yapi 上设计的接口,后端人员需要在后端代码里重新实现一次,而且很容易出现 yapi 上设计的接口和后端代码实现的接口不一致的情况,如:字段缺失、数据类型不一致等等。有没有方法可以通过 API 文档自动生成代码呢?前端有很多工具可以从 swagger API 文档自动生成代码,但生成后端的工具暂未发现(或不实用),从后端代码生成 swagger 的但是有很多(比如:springfox)。所以,传统模式下通常是这种情况:
前端等着后端设计 API。或者通过独立的 API 管理工具来设计接口,然后后端再参照实现;
前端根据 API 管理工具/或后端生成的 swagger 文档手工编写交互 model 和 service,或者通过自动化 codegen 工具从 swagger 自动生成;
如果有变更,继续重复上述两个步骤。
理想的 API 设计
从上面可以看出,无论是等后端设计还是通过 API 管理工具来设计都会有某一方或两方都需要参照 API 文档来手工实现代码逻辑的问题,都会存在 API 与代码实现脱节的问题。理想情况下,我们希望 API 设计/生成方式具备如下特性:
利于前/后端协作,API 定义文件既是 活文档;
API 设计独立于代码实现,可以通过设计好的 API 文档自动生成前/后端代码;
API 设计好了,前/后端即可分别开发,不需要等着某一方提供后才能着手开发;
API 有修改时,前/后端都可以自动化的响应变更,而不需要通过手动编码的方式去查找哪些字段、哪些接口有变化;
通过 API 文档生成的接口代码应该是易用的、性能优化的;
API 应该具备一定的向后兼容性;
Mock 真的需要吗?
基于以上几点,笔者觉得 gRPC(或者其它类似的工具)是现阶段很合适的(以后可能会有更好的技术) API 设计工具。
gRPC
gRPC 是 Google 推出的一款 RPC 工具,本文不会对 RPC 原理做过多介绍,也不会详细的讲解 gRPC 本身,将专注于介绍用 gRPC 来设计 API 的好处上。对于 RPC 以及 gRPC 本身,后续文章再对其作进一步介绍。
gRPC 是通过 .proto 文件来设计的,也就是说它可以独立于前/后端设计、管理。这样,在开发早期,前/后端人员都可以对其进行设计,而且因为 .proto 文件只是纯文本文件,可以使用源代码管理工具(如:Git、SVN等)对其进行管理。这从 API 版本的管理上比使用类似 yapi 这样独立的 API 管理工具来说更好,也不需要为此单独部署一套软件。这就解决了在 API 设计/管理层面上前/后端协作的问题。
gRPC 的设计是独立于代码实现的,它有一套自己的语法规则(非常简单,很快就能上手),而且官方和社区提供了大部分编程语言/框架的代码生成。这就解决了代码实现与 API 文档不一致的问题,一但 API 有调整,通过工具即可自动生成最新的代码,后端服务及数据模型,前端访问接口服务及数据模型。
gRPC 是通过二进制数据传输的,而且提供了数据压缩等特性,相比传统的 JSON 格式,它有着更快的 CPU (序列化/反序列化)性能以及更小的网络数据传输流量(二进制、数据压缩)。
gRPC 的向后兼容性通过 Protobuf 体现,Protobuf Message 有两个特点:默认值和字段序号,可以较好地解决兼容性问题。这体现在:
默认值:从设计层面还在纠结字段 null 的情况,所有字段若不设置都会有默认值(除了 String 的默认值为空字符串外和集合类型的默认值为不可变空集合外,其它对像类型字段的默认值均为 null);
字段序号:字段名字的改变,只要保证对应的序号不变或不起冲突,那接口就是兼容的,不需要前/后端的 .proto Message 设计完全一致。
message User {
int64 id = 1;
string username = 2;
int64 create_time = 3;
}
真的需要 Mock 吗?
Mock,准确地说是类似于 yapi 这样的 API 管理工具提供的数据 Mock,真的需要吗?首先来聊聊 Mock 想要解决和实际解决得怎样的问题:Mock 想解决后端还未实现接口时,前端可以访问后端服务的问题。
但实际上,它解决得并不好;而且,通常情况下我们也不需要它。Mock 生成的数据通常都是一些无意义的字符串或一些随机的数值、日期,当然,有些工具提供 js 脚本可以自定义设计 mock 生成规则……但这些都更像是掩耳盗铃,前端需要的是真实的、能向前走通业务流程的接口数据,而 Mock 是做不到的!若做到了,那就不需要 Mock,因为你已经在实现真实的业务逻辑。所以,正确的做法应该是后端通过 gRPC 自动生成服务接口,并在接口层返回一些固定的模拟数据即可,前端在做 UI 组件和 前端侧业务逻辑 时只要保证调用后端接口不报错就行,这并不需要太多的时间。
从另一个角度来说,gRPC 可以生成完整的前端代码,配以类似 Typescript 等静态类型语言。在开发早期(后端还未提供任何一个返回真实业务数据接口的时候),前端是可以直接写业务逻辑的,因为这时前端已经拥有了完整的业务数据 model 和 API 接口访问请求代码。在这一阶段,前/后端是可以独立、并行的进行代码开发的。待后端提供了任何一个可用的服务 API,即可进行前/后端联调。
GraphQL
GraphQL 也是一套很好地用来设计 API 的工具,当然也可以选择使用 GraphQL。在某些业务上它具备 gRPC 没有的灵活性。不过,gRPC 可以统一前/后端交互与后端服务内部 RPC 的完整技术栈,从这一点看它可以减少学习成本。
小结
本文阐述了笔者对使用 gRPC 来设计 API 的一些思考,它可以解决传统 RESTful 及使用单独的 API 管理工具来设计 API 的缺陷。同时,通过在 gRPC 所使用 .proto 文件里添加注释,本身就是(活)文档,不需要再额外管理 API 文档。
使用 gRPC 也会有一些问题,比如生态还不够丰富、相关学习资料偏少。以及缺少强大的测试工具,不过这可以通过开发些小的工具软件来解决。
之后会写系列文章来介绍怎样具体使用 gRPC 设计 API。敬请期待!