原文 地址: 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
有所有数据描述符所需的支持(
- 用于列表,
- 用于条目,
用于数据元素)。它也有足够的动作描述符支持(用于安全
链接,