前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再谈 API 的撰写 - 契约

再谈 API 的撰写 - 契约

作者头像
tyrchen
发布2018-03-28 16:01:44
1.5K0
发布2018-03-28 16:01:44
举报
文章被收录于专栏:程序人生程序人生

现代社会是个契约社会,生活中大大小小的事情都在和契约打交道。去奥莱买件衣服,一纸小票,便是你跟商家的契约:你花钱买到了产品,产品的问题商家会承诺处理(退换货)。如果你用信用卡交款,你和银行之间,银行和商家之间又达成了一系列契约:银行会在未来的某个时刻扣除你的 credit,这 credit 你需要用钱来赎回;银行同时欠下商户几乎等值的 credit,这 credit 会在月末付给商户。

契约

契约在软件上最基本的体现就是函数。当一个函数被定义出来时:它告诉它的使用者,你我之间应该如何合作。

比如说,一个函数可以是这样定义的:如果你传递给我类型为 X 的数据,我会返回给你类型为 Y 的结果,而且如果你传递相同的值进来,我给你相同的结果。这是 pure function,也是程序员最喜欢的契约形式,因为黑纸白字,清清楚楚,童叟无欺。

更普遍的情况是不那么纯粹的函数:如果你传递给我类型为 X 的数据,我会返回给你类型为 Y 的结果,当然,如果结果不存在,我会给你个 null,爱谁谁;而且,如果我中间处理的过程出了差池,我会扔一颗或者若干颗叫「异常」的炸弹,咱俩要么哥俩好(你处理异常),要么同归于尽(不处理)。此外,我不能保证你传递相同的值进来,都返回给你相同的结果(比如说数据库操作)。有副作用的函数尽管有诸多含混不清的地方,任然不失为一种契约。

函数级别的契约的所有当事人都是程序员,契约更新的影响面有限,所以遇到问题,姐弟俩一商量,改!新的契约就出现了。然而,新的契约出现并不意味着旧的契约的终止,只有当所有使用旧契约的地方都改用新契约时,我们才能安全地废除旧契约。就一个函数来说,如果是两人之间的事,更换契约也就是个把小时的事情;然而,像 linux 这样复杂的系统,你改一个 list_add_tail() 的接口试试(假设你有权限),即使 Linus 不拍死你,我保证社区的口水也要淹死你。为啥?你触动了很多人的奶酪。

铺垫了这么多,就是想说明一件事:一旦你制定了一纸契约,你必须遵守它,且不要轻易改动它;使用契约的人越多,改动的代价越大。我们定义一个陈氏指数 CEI —— 契约使用指数(Contract Employ Index),每百万使用者记为 1。CEI 越高,表明使用者越多,同样的,改动的代价就越大。

REST API(以下凡提到 API,都指 REST API)是什么?REST API 是服务器和客户端之间的契约。这就意味着一个中小规模的 API,其 CEI 起码在 0.1 以上。API 一旦发布,你基本失去了对其任意修改的权利,因为你无法期待脱离了掌控的客户端能够像我们希望的那样,步调一致地升级系统。

所以,即便你习惯于随心所欲地创建一个函数,然后在需要的时候重构之,做 API 时,你会受到很多掣肘。老子说:「夫轻诺必寡信,多易必多难」,你一开始随意了,简单了,会给之后的维护和更新带来无穷无尽的痛苦。

所以我们需要好好进行设计 API 的接口。

定义和设计契约

我们知道,设计接口并不是一件轻松的活,我们要考虑:userability,simplicity,security,reliability 等等,设计好了还需要将其文档化。所以我们最好借助于工具的力量来设计 API,就像我们使用 visio 设计网络拓扑或者软件架构一样。目前比较流行的 API 接口设计工具有 swagger,API blueprint 和 RAML。

它们共同的特点是你可以很方便地描述 API 的输入输出,并生成交互式的 API 文档。所谓交互式 API 文档,是指用户在读 API 文档的时候,可以在线运行 API,获得结果。这样,API 的设计者就可以在还没有开始写代码的时候就反复推演 API 的结构,直到产生一个健壮的,清晰明了,可用性强的接口。

Swagger

swagger 是最早也是最成熟的 API 接口设计工具。它可以使用 json/yaml 来描述 API 的接口,使用 swagger 来设计和描述 API 有很多好处:API 的文档化,API 的接口的可视化,各种语言的客户端类库的自动生成,甚至服务端代码也能够自动生成。包括代码生成工具在内的完整而成熟的工具链是 swagger 的杀手锏,也是众多 API 厂商优先选择 swagger 的一个重要因素。

我们看 swagger 的一个例子(instagram API):

这里定义了一个 API endpint /users/self/feed,他接受三个 querystring 参数,并在请求成功时(200)返回一个这样的对象:

代码语言:javascript
复制
{    "data": [...]
}

swagger 的缺点是太繁杂,撰写起来很麻烦。不过,这不是什么大问题,所以我最终选择了 swagger。

API Blueprint

API Blueprint 更偏向 API 的文档化,所以它选择的描述语言是 markdown。三者之间 API blueprint 的描述语言可读性最强,更像是真的在撰写文档。

然而,markdown 的强项不在表述语法,validation 相关的内容用 markdown 描述不是很舒服,看别人写的文档很容易明白,自己写起来就会错漏百出。API blueprint 的工具链也是个薄弱环节,很多工具都没有或者不成熟。如果说工具的缺乏还可以通过时间来弥补,使用 markdown 这种对机器不太友好的定义语言来定义各种语法,则是 API blueprint 犯下的大错。因为,对比三者的语法,它们的学习曲线都很长,遗忘指数都很高(不是经常用),指望程序员来写还不如指望机器帮你生成。而机器生成强语法结构的 json / yaml 相对简单,生成弱语法结构的 markdown 则要填不少坑。

所以,权衡之下,三者之间,我最先淘汰的是 API blueprint。

RAML

RAML 使用 yaml 来描述 API。它被设计地很灵活,很容易把描述分解到多个文件里然后相互引用。

就描述语言来说,RAML 像是一个蓬勃向上的少年,精明而干练;而 swagger 已经垂垂老矣,冗长而乏味。我一开始在 RAML 和 swagger 两者间左右摇摆,写了不少测试代码,如果不是 swagger 的工具链过于吸引人,而 RAML 1.0 版本还处在 beta 阶段,我可能会最终选择 RAML。

契约和实现合二为一

如果我们从 swagger 出发,设计好 API 的接口,然后再用某种语言实现这个接口,显得有些累赘,日后改接口时,得改代码;改代码后如果变动了接口,还得回头改 swagger 的声明,这样太累心,迟早会出不一致的问题。一旦不一致,之前所做的所有努力就泡汤了:你提供了契约,却没有按照契约去行事。

swagger 考虑到了这一点,它能帮你生成客户端的 SDK 和服务器端的 stub。客户端的 SDK 还好,客户端的其他代码都是单向调用 SDK,重新生成并不会影响太大;服务端的代码需要 API 实现者实现,即便生成了 stub,肯定是要修改和添加功能的,所以如果修改 swagger 文档后,你不能再重新生成服务器端的 stub 了,因为这样有可能覆盖掉你已经修改的代码。所以大家使用 swagger 的方式基本都是服务器这端完全自己写,不用 stub。这样的话,上述的问题依旧存在。

另一种解决方案是通过 API 代码反向生成 swagger 文档。乍一看这似乎违背了 API 描述语言的初衷:我们竟然在没开始设计之前,就开始写代码了。

不必过虑。我们可以把代码的结构调整地更贴近描述语言。你可以先撰写代码把 API 的输入输出定义清楚,然后通过这个定义来生成 swagger 文档,在 swagger-ui 里面调试和验证;当借口设计符合期望后,再完成具体的实现。比如说这样用代码描述 API:

抛开 action 是什么不提,这段代码几乎和你用 YAML 描述 API 的接口如出一辙(这里缺了描述 response 的内容)。我们可以使用它生成 swagger 文档来验证:

通过代码反向生成 swagger 文档的好处是代码和文档总是一致的,API 的实现和契约相互印证;缺点是程序员看见代码就像看见九天仙女一样,眼迷心荡,刚定义好接口,还未细思,就忙不迭地去实现了。

尾声

这个系列竟然写出了五篇文章,大大出乎我的意料。韩非子说:善张网者,引其纲。不一一摄万目而后得。做 API 是个提纲携领的活,你要从纷扰的「万目」中找到那根系网的大绳,牵动之,网就搭好了。这根绳,不消说,就是我在 再谈 API 的撰写 - 架构 那篇文章中所述的 Pipeline。以此为纲,自顶向下,层层递进,你便豁然开朗。然而,API 写得再好,没有一个与之对应的契约,是万万不行的 —— 没有文档描述的 API 就如同没有说明书的产品。文档和代码,如同泉水干涸之后相呴以湿,相濡以沫的鱼儿,谁也离不开谁。能否防止它们随着时间的流逝功能的增加而相忘于江湖,是考量每个程序员能力和操守的一杆秤。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 契约
  • 定义和设计契约
    • Swagger
      • API Blueprint
        • RAML
        • 契约和实现合二为一
        • 尾声
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档