导读
在以云计算为主角的开发者视界中,OpenAPI 是绝对的主角。要发短信,用 OpenAPI;要管理资源,用 OpenAPI;要管理权限,用 OpenAPI。如果一个 OpenAPI 解决不了你的问题,那就再来一个。在今天,开放平台及 OpenAPI 随处可见,它是系统与系统之间集成的重要桥梁。但 OpenAPI 用起来是否真的舒服,这要打一个大大的问号。本文将介绍 OpenAPI 领域下的难题和一些解决方案。
背景
阿里云有位工程师叫朴灵,热爱开源,是活跃在 Github 上的国内技术大牛之一。在阿里工作 6 年之际,朴灵产生了离职的想法,打算去一家创业公司再战高峰。走之前,朴灵做了一些研究工作,他发现阿里云在功能和产品上可以说是一流的云计算厂商,是创业公司的首选,但由于过去的业务中写过大量的 Node.js SDK,对开发者体验有着自己的体感,他觉得在开发者体验关怀上,阿里云做得还不够好。来自一个热血工程师最朴素的想法,自己何不先留下来,去把这件事情做好,于是,朴灵加入了阿里云开放平台负责 SDK 业务,期间,他和团队研发了专利 Darabonba(原名TeaDSL),下面朴灵将分享 Darabonba 如何解决多语言 SDK 的问题。
使用 OpenAPI 的痛苦
在过去,我们经常说的 OpenAPI,通常的做法是,开发好服务端的接口,然后在文档里简单写几个参数描述,就直接丢给客户去用。反正我是开发好了,我这里是好的,客户能不能用起来我是不用管的。
图 1 第一代的 OpenAPI 通常仅由简单的文档及实际的接口构成
然而接下来的问题就来了。首先,文档上写得不清不楚的参数,没有试过,完全不知道它到底能不能 Work。其次,OpenAPI 总得有一定的权限认证吧,那么总得有一个签名啥的,每个客户都要写一遍,关键是总是没法写对。再次,不同的客户所使用的编程语言不一样,得把接口重新包装才能用。
总算费心费力调通了接口,以为可以高枕无忧的时候,咋接口老是报错,网络连不上,返回的数据不对,诸如此类。再往后,OpenAPI 可能总是要发生一点变化什么的,总是出现一些数据结构发生变化,不兼容之类的问题。
一个 OpenAPI 到最后,不光是用户使用起来觉得很气,作为维护者也是很艰难的。当公布一个 OpenAPI 后,第一步给出简单的文档后,会发现除了要把参数详情写得越来越完善准确外,还得给出签名算法,让不同语言的开发者来接入。然而给出签名算法后,会发现只有一些开发者能顺利完成,大部分的开发者只能眼巴巴地请你帮忙提供一个 SDK。好吧,那就提供一下我最拿手的 Java 语言的签名,提供一个核心 SDK 呗。
图 2 第二代的 OpenAPI 会有 SDK 的实现,但仅有少许的语言支持
随着这个 OpenAPI 接口的用户越来越多,一个客户说我要用 C++ 来对接你,另一个客户说我要用 Python 来对接你,于是,我一个 Java 程序员,怎么就要写那么多语言的 SDK 呢。没有办法,如果不提供良好的 SDK,客户说,没有 IDE 提示呢,我怎么写代码呢。
总而言之,在 OpenAPI 的应用过程中,一件简单的事情,会变得非常复杂:
- 需要提供良好的 API 文档,作为最基本的要求
- 需要提供 SDK,保障开发者的编码体验,封装细节,代码提示等
- 需要提供 Code Sample,更理解接口的使用效果
- 如果有 CLI 就更好了,这样连 bash 脚本写起来也更方便
- 如果没有 Test Cases 作为日常的持续集成,接口质量可能存在问题
上面这些要求,如果加上多种编程语言的条件,就会演变为一件细碎而又繁多的体力活。并且这中间不能有任何的变动,因为仅仅是一点点的 OpenAPI 变动,就需要连带整个下游发生变化。如果一个地方没有保持一致,那么客户问题就会出现。
图 3 当用户量变多,OpenAPI 的提供者需要提供完善的工具及更多的编程语言支持
通常为了解决此类的问题,以及 OpenAPI 的诸如签名校验,限流,生成 SDK、文档等等,业界通常会使用 API 网关来承担这些横向的责任。
然而,作为笔者所在的环境下,会发现,我们身边的网关有点多。于是不同的网关有不同的风格,不同的签名算法,不同的序列化格式。于是上述的过程要根据不同网关的数量,进行翻倍:
图 4 当一个企业变得庞大时,不同风格的 OpenAPI 及网关都会出现
当我们在抱怨使用不同产品的 OpenAPI/SDK 体验不一致,文档不对,Demo 出错等等问题时,真不是因为做这些事情太难,而是太多,太琐碎。一件简单的事情,需要做一百次,也就不是简单的事情了。
Darabonba 的解决之道
Darabonba 是由阿里云开放平台 SDK 团队主导设计的一门领域特定语言。主要用于解决如下问题:
- 通过一门中间语言,可以支持不同风格的网关。即使网关下的 OpenAPI 风格各异,也能一致地表达到。
- 可以通过翻译的能力,实现对不同编程语言的代码生成。也就是可以基于统一的中间表达,生成多语言的 SDK。
- 基于中间表达,我们可以将一组 OpenAPI 视为一个 library,因此可以在这个基础上实现 OpenAPI 接口的 Code Sample 编写。进而实现多语言的 Code Sample 统一生成。
因此 Darabonba 的核心能力就是通过一种中间语法来描述 OpenAPI,提供类似编程语言的能力,来将 OpenAPI、SDK、Code Sample 等场景及语言有机地结合在一起。
在没有 Darabonba 之前,对于不同的网关,我们要为它制定独立的工作流程,即从 OpenAPI 定义到不同语言的 SDK 生成,是独特的。换一个新的网关风格,就要重新实现这套流程。
图 5 M 个网关都要支持 N 种编程语言,整个工作量是 M * N 的关系
而具有 Darabonba 后,我们则形成一个中间层。可以将原来的工作收敛起来,我们仅需要关注不同的网关到 Darabonba 的转换工作,以及 Darabonba 到各个编程语言的生成工作。
图 6 经过中间层的隔离,整个工作量变为 M + N 的关系
也就是说,Darabonba 是在做一件 M * N 到 M + N 的工作。当网关越多,支持的编程语言越多,收益则越大。
一旦这个中间层建立起来,整个 OpenAPI 的应用形式都可以基于它来构建。比如,编写一个 OpenAPI 的 Code Sample,Test Case 等。
接下来简单介绍 Darabonba 是如何实现支持任意风格的网关和多种编程语言的。
如何支持任意风格的网关
对于不同的 API 网关,或者不同产品的 OpenAPI 而言,它们之间的风格可能都千差万别,因此在很大的程度上,每种风格的 OpenAPI 都有它自己的元数据定义格式。为了减少网关、风格带来的差异化,业界主要推动的方式是尽量采用标准的定义格式。比如 Swagger 就是其中的佼佼者,它依托于 OpenAPI Specification ,以 RESTful 风格的 OpenAPI 作为基准,形成了一套业界标准。
但这个世界就是这样不完美,我们现有的大量 OpenAPI 并不是 RESTful 风格的。这导致很多的产品现存的 OpenAPI 在文档、SDK等场景下,无法使用上 Swagger 这样强大的生态工具链。
为了解决这些问题,我们需要进行两步操作:
- 设立一套新的标准,来包容不同风格的 OpenAPI
- 以这套新的标准,来建设生态工具链
如果完成这两个步骤,那么现实世界上的每一个 OpenAPI,RESTful 或者非 RESTful 的,不需要做任何迁移,也能具有强大的工具链支持。
新标准的设计
通过我们的研究发现,无论 OpenAPI 的参数是如何组成的,传输是 JSON,还是 XML,乃至自定义协议,OpenAPI 都是基于 HTTP 协议栈进行提供的。也就是说,万变不离其宗的是 HTTP 协议本身。因此我们确立的基本模型是这样的:
{
protocol: string, // http or https
port: number, // tcp port
host: string, // domain
request: {
method: string, // http method
pathname: string, // path name
query: map[string]string, // query string
headers: map[string]string, // request headers
body: readable // request body
},
response: {
statusCode: number, // http method
statusMessage: string, // path name
headers: map[string]string, // response headers
body: readable // response body
},
}
对于不同风格的 OpenAPI 而言,就像不同风格的建筑,它们的建筑材料都几乎相同,只是施工手法,组合形式不一样而已。我们看到的 OpenAPI 风格差异,实质则是序列化过程不同而带来的不同。我们序列化过程和数据模型分离,将用户更直观的数据结构提取出来。
比如从用户角度出发,一个数据模型是更直观的事物:
model User {
username: string,
age: number
}
在不同的网关下,它的传输形式可能是 JSON,也可能是 XML,但最终都是 readable,也就是可读的字节流。
toJSON(user: User): string
toXML(user: User): string
最终的结果就是:
__request.body = toJSON(user);
__request.body = toXML(user);
更进一步的过程是,我们会将一个 OpenAPI 的请求/响应包装为一个类似于编程代码的方法:
api getUser(username: string): User {
__request.method = 'GET';
__request.pathname = `/users/${username}`;
__request.headers = {
host = 'hostname',
};
} returns {
var body = readAsJSON(__response.body);
return body;
}
尽管上面的代码不能实际运行,但大致也看出来我们包容不同的网关、风格的办法如下:
- 以 request / response 也就是 HTTP 协议作为核心模型
- 通过引入一些方法,如 toJSON / toXML / readAsJSON 等方法来分离数据结构和序列化过程
- 将整个过程包装成方法
这些方法在不同的编程语言下具有不同的实现,但我们只要定义好统一的签名,就能确保一致性:
function toXML(data: $Model): string;
function toJSON(data: $Model): string;
以上就是 Darabonba 如何实现支持任意网关的方案。整个过程相对抽象,网关间的那些具有差异化的风格,统统交给这些方法去实现,留下来的就只有数据结构。
如何支持不同的编程语言
如果只是能通过一种描述方式来描述不同的 OpenAPI 调用过程,只是完成了一半的工作。另一半的工作是如何将这种描述语言落地到不同的编程语言下。在过去,我们支持不同的编程语言,主要是基于模版的形式来生成不同语言的实际代码。但这对我们来说仍然还有一些不足之处:
- 模版的生成方式相对生硬,实现起来容易,但维护起来不那么灵活
- 生成出来的代码容易带来命名冲突,语法错误等
从上面的形式也看到,这个方案,被我们设计成了一种 DSL 代码。因此它是具有自己的词法、语法、语义规则的,在生成目标编程语言代码之前,会有一套自身的校验。DSL 的这些能力是模版所不具备的。
可能对于别的场合,采用 DSL 的形式并不多见。但对于前端工程师而言,这些年已经见的较多了:CoffeeScript、Babel、JSX、TypeScript 等等。为此我们参考了诸多编程语言的设计,最终形成了自己的一套语法。并借鉴编译器领域的转译方式,因此我们可以在模型一致的情况,生成到各种不同的编程语言下。
整个 Darabonba 的处理流程如下:
最终我们支持多种编程语言的场景主要有3个:
- 基本的多种语言的 SDK
- OpenAPI 相关的多种语言的 Code Sample
- OpenAPI 相关的多种语言的 Test Case
通过中间语言的强校验,生成到多种目标场景,可以解决编程语言支持不全面的问题。同时也大幅节约 OpenAPI 维护者的精力成本,不需要反复手工地编写不同编程语言下的 Code Sample。随着对不同编程语言的支持逐步完善,这些中间 Darabonba 代码不需要任何操作,即可自动支持到新的编程语言下。
总结
Darabonba 的主要能力是支持到不同风格的 OpenAPI,同时支持多语言的 SDK、Code Sample 目标生成。最终的目的仍然是打通从 OpenAPI 定义到文档、到 SDK、CLI 等 OpenAPI 使用场景下的一致性。提供给用户更统一、专业、一致的使用体验。同时也大幅降低 OpenAPI 提供者用来支持用户的成本,通过自动化的方式,节省精力的同时,还减少人为参与时导致的错误。
目前 Darabonba 在阿里云的一些 SDK 上已经有所应用,如:https://github.com/aliyun/aliyun-ccp。阿里云开放平台在持续努力提升它的整个工具支持生态,以期望能建成比 Swagger 更适配的生态体系。