Web API 设计方法论

 原文 地址: http://yuedu.baidu.com/ebook/4a2837874693daef5ef73df2?pn=1&rf=http%3A%2F%2Fwww.infoq.com%2Fcn%2Fminibooks%2Farchitect-201502

 

Web API 设计方法论

作者Mike Amundsen,译者吴海星

为Web 设计、实现和维护API 不仅仅是一项挑战;对很多公司来说,

这是一项势在必行的任务。本系列将带领读者走过一段旅程,从为API

确定业务用例到设计方法论,解决实现难题,并从长远的角度看待在

Web 上维护公共API。沿途将会有对有影响力的人物的访谈,甚至还

有API 及相关主题的推荐阅读清单。

这篇InfoQ 文章是Web API 从开始到结束系列文章中的一篇。你可以

在这里进行订阅,以便能在有新文章发布时收到通知。

设计Web API 不止是URL、HTTP 状态码、头信息和有效负载。设计

的过程——基本上是为了API 的“观察和感受”——这非常重要,并

且值得你付出努力。本文简要概括了一种同时发挥HTTP 和Web 两者

优势的API 设计方法论。并且它不仅对HTTP 有效。如果有时你还需

要通过WebSockets、XMPP、MQTT 等实现同样的服务,大部分API

设计的结果同样可用。可以让未来支持多种协议更容易实现和维护。

优秀的设计超越了URL、状态码、头信息和有效负载

一般来说, Web API 设计指南的重点是通用的功能特性,比如URL

设计,正确使用状态码、方法、头信息之类的HTTP 功能特性,以及

持有序列化的对象或对象图的有效负载设计。这些都是重要的实现细

节,但不太算得上API 设计。并且正是API 的设计--服务的基本功能特

性的表达和描述方式--为Web API 的成功和可用性做出了重要贡献。

一个优秀的设计过程或方法论定义了一组一致的、可重复的步骤集,

可以在将一个服务器端服务组件输出为一个可访问的、有用的Web API

时使用。那就是说,一个清晰的方法论可以由开发人员、设计师和软

件架构师共享,以便在整个实现周期内帮助大家协同活动。一个成熟

的方法论还可以随着时间的发展,随着每个团队不断发现改善和精简

过程的方式而得到精炼,却不会对实现细节产生不利的影响。实际上,

当实现细节和设计过程两者都有清晰的定义并相互分离时,实现细节

专题| Topic

的改变(比如采用哪个平台、OS、框架和UI 样式)可以独立于设计过

程。

API 设计七步法

接下来我们要对Richardson 和Amundsen 合著的《REST 风格的Web

API》一书中所介绍的设计方法论做简要地概述。因为篇幅所限,我们

不能深入探讨这一过程中的每一步骤,但这篇文章可以让你有个大概

的认识。另外,读者可以用这篇概述作为指南,根据自己组织的技能

和目标开发一个独有的Web API 设计过程。

说明:是的,7 步看起来有点儿多。实际上清单中有5 个步骤属于设计,

额外还有两个条目是实现和发布。最后这两个设计过程之外的步骤是

为了提供一个从头到尾的体验。

你应该计划好根据需要重新迭代这些步骤。通过步骤2(绘制状态图)

意识到在步骤1(列出所有组成部分)有更多工作要做。当你接近于写

代码(步骤6)时,可能会发现第5 步(创建语义档案)中漏了一些东

西。关键是用这个过程暴露尽可能多的细节,并愿意回退一步或者两

步,把前面漏掉的补上。迭代是构建更加完整的服务画面以及澄清如

何将它暴露给客户端程序的关键。

步骤1 : 列出所有组成部分

第一步是列出客户端程序可能要从我们的服务中获取的,或要放到我

们的服务中的所有数据片段。我们将这些称为语义描述符。语义是指

它们处理数据在应用程序中的含义,描述符是指它们描述了在应用程

序自身中发生了什么。注意,这里的视点是客户端,不是服务器端。

将API 设计成客户端使用的东西很重要。

比如说,在一个简单的待办事项列表应用中,你可能会找到下面这些

语义描述符:

 id:系统中每条记录的唯一标识符;

 title:每个待办事项的标题;

 dateDue:待办事项应该完成的日期;

 complete:一个是/否标记,表明待办事项是否已经完成了。

在一个功能完备的应用程序中,可能还会有很多语义描述符,涉及待

办事项的分类(工作、家庭、园艺等),用户信息(用于多用户的实

现)等等。不过为了突出过程本身,我们会保持它的简单性。

专题| Topic

步骤2 : 绘制状态图

下一步是根据建议的API 绘制出状态图。图中的每个框都表示一种可

能的表示——一个包含在步骤1 中确定的或多个语义描述符的文档。

你可以用箭头表示从一个框到下一个的转变——从一个状态到下一个

状态。这些转变是由协议请求触发的。

在每次变化中还不用急着指明用哪个协议方法。只要标明变化是安全

的(比如HTTP GET),还是不安全/非幂等的(比如HTTP.POST),

或者不安全/幂等的(PUT)。

说明:幂等动作是指重复执行时不会有无法预料的副作用。比如

HTTP PUT ,因为规范说服务器应该用客户端传来的状态值替换目标资

源的已有值,所以说它是幂等的。而HTTP POST 是非幂等的,因为规

范指出提交的值应该是追加到已有资源集合上的,而不是替换。

在这个案例中,我们这个简单的待办事项服务的客户端应用程序可能

需要访问可用条目的清单,能过滤这个清单,能查看单个条目,并且

能将条目标记为已完成。这些动作中很多都用状态值在客户端和服务

器之间传递数据。比如add-item 动作允许客户端传递状态值title 和

dueDate。下面是一个说明那些动作的状态图。

专题| Topic

这个状态图中展示的这些动作(也在下面列出来了)也是语义描述符--

它们描述了这个服务的语义动作。

 read-list

 filter-list

 read-item

 create-item

 mark-complete

在你做这个状态图的过程中,你可能会发现自己漏掉了客户端真正想

要或需要的动作或数据项。这是退回到步骤1 的机会,添加一些新的

描述符,并/或者在步骤2 中改进状态图。

在你重新迭代过这两步之后,你应该对客户端跟服务交互所需的所有

数据点和动作有了好的认识和想法。

步骤3:调和魔法字符串

下一步是调和服务接口中的所有“魔法字符串”。“魔法字符串”全

是描述符的名称--它们没有内在的含义,只是表示客户端跟你的服务通

讯时将要访问的动作或数据元素。调和这些描述符名称的意思是指采

用源自下面这些地方的,知名度更高的公共名称:

 Schema.org

 microformats.org

 Dublin Core

 IANA Link Relation Values

这些全是明确定义的、共享的名称库。当你服务接口使用来自这些源

头的名称时,开发人员很可能之前见过并知道它们是什么意思。这可

以提高API 的可用性。

说明:尽管在服务接口上使用共享名称是个好主意,但在内部实现里

可以不用(比如数据库里的数据域名称)。服务自身可以毫不困难地

将公共接口名称映射为内部存储名称。

以待办事项服务为例,除了一个语义描述符create-item,我能找到所有

可接受的已有名称。为此我根据Web Linking RFC5988 中的规则创建了

一个具有唯一性的URI。在给接口描述符选择知名的名称时需要折中。

它们极少能跟你的内部数据存储元素完美匹配,不过那没关系。

这里是我的结果:

 id -> 来自Dublin Core 的identifier

专题| Topic

 title - 来自Schema.org 的name

 dueDate -> 来自Schema.org 的scheduledTime

 complete -> 来自Schema.org 的status

 read-list -> 来自IANA Link Relation Values 的collection

 filter-list -> 来自IANA Link Relation Values 的search

 read-item -> 来自IANA Link Relation Values 的item

 create-item ->用RFC5988 的http://mamund.com/rels/create-item

 mark-complete - 来自IANA Link Relation Values 的edit

经过名称调和,我的状态图变成了下面这样:

步骤4:选一个媒体类型

API 设计过程的下一步是选一个媒体类型,用来在客户端和服务器端之

间传递消息。Web 的特点之一是数据是通过统一的接口作为标准化文

档传输的。选择同时支持数据描述符(比如"identifier"、"status"等)和

动作描述符(比如"search"、"edit"等)的媒体类型很重要。有相当多可

用的格式。

在我写这篇文章时,一些顶尖的超媒体格式是(排名不分先后):

 超文本标记语言(HTML)

 超文本应用程序语言(HAL)

专题| Topic

 Collection+JSON (Cj)

 Siren

 JSON-API

 交换表达式的统一基础(UBER)

让所选择的媒体类型适用于你的目标协议也很重要。大多数开发人员

喜欢用HTTP 协议做服务接口。然而WebSockets、XMPP、MQTT 和

CoAP 也会用--特别是对于高速、短消息、端到端的实现。

在这个例子中,我会以HTML 为消息格式,并采用HTTP 协议。HTML

有所有数据描述符所需的支持(<UL>用于列表, <LI>用于条目,

<SPAN>用于数据元素)。它也有足够的动作描述符支持(<A>用于安全

链接, <FORM method="get">用于安全转变, <FORM method="post">

用于非安全转变)。

注意:在这个状态图中, “编辑”动作是幂等的(比如HTTP PUT),

并且HTML 仍然没有对PUT 的原生支持。在这个例子中,我会添加一

个域来将HTML 的POST 做成幂等的。

好了,现在我可以基于那个状态图创建一些样例表示来“试试”这个

接口了。对我们的例子而言,只有两个表示要渲染:“待办事项列表”

和“待办事项条目”表示。

专题| Topic

图1 用HTML 表示待办事项列表集合

图2 用HTML 表示待办事项条目

专题| Topic

记住,在你做状态图的表示样例时,可能会发现之前的步骤中有所遗

漏(比如漏掉描述符,动作描述符中有幂等之类的变化等)。那也没

关系。现在就是解决所有这些问题的时机-- 在你把这个设计变成代码

之前。

等你对表示完全满意之后,在开始写代码之前还有一个步骤——创建

语义档案。

步骤5: 创建语义档案

语义档案是一个文档,其中列出了设计中的所有描述符,包括对开发

人员构建客户端和服务器端实现有帮助的所有细节。这个档案是一个

实现指南,不是实现描述。这个差别很重要。

服务描述符的格式

服务描述文档格式已经出现了相当长一段时间了,并且当你想给已有

的服务实现生产代码或文档时很方便。确实有很多种格式。

在我写这篇文章时,顶级竞争者有:

 Web 服务定义语言(WSDL)

 原子服务描述(AtomSvc)

 Web 应用程序描述语言(WADL)

 Blueprint

 Swagger

 REST 风格的应用程序建模语言(RAML)

 档案的格式

 现在只有几种档案格式。我们推荐下面两种:

 应用级语义档案(ALPS)

 JSON-LD + Hydra

这两个都比较新。JSON-LD 规范在2014 年早期达成了W3C 推荐状态。

Hydra 仍是一个非官方草案(本文写成时还是),有一个活跃的开发者

社区。ALPS 仍处于IETF 的早期草案阶段。

因为档案文档的理念是要描述一个问题空间的现实生活方面(不只是

那一空间中的单一实现),所以其格式跟典型的描述格式十分不同。

专题| Topic

图3 ALPS 格式的待办事项列表语义档案

你会注意到,这个文档就像一个基本的词汇表,包含了待办事项服务

接口中所有可能的数据值和动作--就是这个理念。同意遵循这个档案的

服务可以自行决定它们的协议、消息格式甚至URL。同意接受这个档

案的客户端将会构建为可以识别,如果合适的话,启用这个文档中的

描述符。

这种格式也很适合生成人类可读的文档,分析相似的档案,追踪哪个

档案用得最广泛,甚至生成状态图。但那是另外一篇文章的课题了。

现在你有完整的已调和名称的描述符清单,已标记的状态图,以及一

个语义档案文档,可以开始准备编码实现样例服务器和客户端了。

步骤6:写代码

到了这一步,你应该可以将设计文档(状态图和语义档案)交给服务

器和客户端程序的开发人员了,让他们开始做具体的实现。

专题| Topic

HTTP 服务器应该实现在第2 步中创建的状态图,并且来自客户端的请

求应该触发正确的状态转变。服务发送的每个表示都应该用第3 步中

选好的格式,并且应该包含一个第4 步中创建的指向一个档案的链接。

响应中应该包含相应的超媒体控件,实现了在状态图中显示、并在档

案文档中描述的动作。客户端和服务器端开发人员在这时可以创建相

对独立的实现,并用测试验证其是否遵守了状态图和档案。

有了稳定的可运行代码,还有一步要做:发布。

步骤7:发布你的API

Web API 应该至少发布一个总能给客户端响应的URL -- 即便是在遥远

的将来。我将其称为“看板URL” --每个人都知道的。发布档案文档也

是个好主意,服务的新实现可以在响应中链接它。你还可以发布人类

可读的文档、教程等,以帮助开发人员理解和使用你的服务。

做好这个之后,你应该有了一个设计良好的、稳定的、可访问的服务

运行起来了,随时可以用。

总结

本文讨论了为Web 设计API 的一组步骤。重点是让数据和动作描述正

确,并以机器可读的方式记录它们,以便让人类开发人员即便不直接

接触也能轻松为这个设计实现客户端和服务器端。

这些步骤是:

1、列出所有组成部分

收集客户端跟服务交互所需的所有数据元素。

2、绘制状态图

记录服务提供的所有动作(状态变化)

3、调和魔法字符串

整理你的公开接口以符合(尽可能)知名的名称

4、选择媒体类型

评审消息格式,找到跟目标协议的服务转变最贴近的那个。

5、创建语义档案

编写一个档案文档,定义服务中用的所有描述符。

专题| Topic

6、写代码

跟客户端和服务器端开发人员分享档案文档,并开始写代码测试跟

档案/状态图的一致性,并在有必要时进行调整。

7、发布你的API

发布你的"看板URL"和档案文档,以便其他人可以用他们创建新的

服务以及/或者客户端程序。

在设计过程中,你可能会发现有遗漏的元素,需要重做某些步骤,以

及要做一些折中的决定。这在设计过程中出现得越早越好。将来开发

人员要求用新的格式和协议实现时,你还有可能用这个API 设计。

最后,这个方法论只是为Web API 设计过程创建一种可靠、可重复、

一致的设计过程的一种可能方式。在你做这个例子时,可能会发现插

入一些额外的步骤,或者缩减一些会更好用,并且-- 当然-- 消息格式

和协议决策在不同案例中可能也会发生变化。

希望这篇文章能给你一些启发,让你知道如何给自己的组织以及/或者

团队创建一个最佳的API 设计方法论。

你可能感兴趣的:(Web API 设计方法论)