专栏首页程序人生再谈 API 的撰写 - 契约

再谈 API 的撰写 - 契约

现代社会是个契约社会,生活中大大小小的事情都在和契约打交道。去奥莱买件衣服,一纸小票,便是你跟商家的契约:你花钱买到了产品,产品的问题商家会承诺处理(退换货)。如果你用信用卡交款,你和银行之间,银行和商家之间又达成了一系列契约:银行会在未来的某个时刻扣除你的 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)返回一个这样的对象:

{    "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 就如同没有说明书的产品。文档和代码,如同泉水干涸之后相呴以湿,相濡以沫的鱼儿,谁也离不开谁。能否防止它们随着时间的流逝功能的增加而相忘于江湖,是考量每个程序员能力和操守的一杆秤。

本文分享自微信公众号 - 程序人生(programmer_life),作者:陈天

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-03-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 再谈 API 的撰写 - 架构

    在 再谈 API 的撰写 - 总览 里我们谈到了做一个 API 系统的基本思路和一些组件的选型,今天谈谈架构。 部署 首先要考虑的架构是部署的架构。部署的方案往...

    tyrchen
  • 再谈 API 的撰写 - 总览

    背景 去年我写过一篇文章:撰写合格的 REST API。当时 Juniper 裁掉了我们在德州的一支十多人的团队,那支团队有一半的人手在之前的半年里,主要的工作...

    tyrchen
  • GraphQL,你准备好了么?

    一个多月前,facebook 将其开源了一年多的 GraphQL 标记为 production ready ( http://graphql.org/blog/...

    tyrchen
  • 移动信息化迎来大融合时代 API将掀起一场大规模革命

    对于很多非IT经理所言, API就是实现两个网站或者数据库之间通过互联网通讯的接口代码。随着企业移动信息化进程的加快,API有可能成为未来所有企业的商业利器。 ...

    人称T客
  • 怎么做API设计

    最近,一位同事问我关于“Beautiful API”的例子。我立刻半开玩笑地说:“情人眼里出西施。”当然,为了支持这一点,我很快地阐述了对我来说Beautifu...

    程序你好
  • 什么是API平台?

    API平台这个术语已经被一些具有API管理、完整生命周期API管理甚至术语API网关的供应商作为同义词使用。每个人都喜欢用“平台”这个词来为话题增添趣味,但什么...

    程序你好
  • 警告:有用的警告|让Kubernetes的使用越来越容易

    作为Kubernetes的维护者,我们一直在寻找在保持兼容性的同时提高可用性的方法。在开发特性、分类bug和回答支持问题的过程中,我们积累了有助于Kuberne...

    CNCF
  • 保护您的API的3种方法变得更容易

    跟上黑客是一项耗时的工作。那些具有恶意意图的人似乎总是领先一步 - 或者至少落后于最新的漏洞。

    随心助手
  • Apache Flink 数据流编程模型

    Statefule Stream Processing: 是最低级别(底层)的抽象,只提供有状态的流。它通过ProcessFunction嵌入到DataStre...

    chaplinthink
  • 11款流行的构建和API测试工具盘点

    组织正在改变他们已经在软件应用项目中成功的微服务架构模型,这就是大多数微服务项目使用API(应用程序接口)的原因。我们要为微服务喝彩,因为它相对于其他的模型有各...

    用户1516716

扫码关注云+社区

领取腾讯云代金券