什么是API规范
API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API 则注定会导致系统的后续发展和维护非常困难。在关键环节制定明确的API规范有助于 Service 对内提高产品间互通的效率,对外提供一致的使用体验,也有助于更好地被集成。
对于API规范,比较知名的是 OpenAPI Specfication[1] 和 Google API Design Guide[2] 。前者针对 RESTful API 设计在细节层面给出了非常具体的规定,已经成为 RESTful API 设计领域的事实标准,而后者则主要从云厂商的角度提出许多最佳实践性质的规范与建议,这些原则不仅仅适用于 RESTful API,也适合其他类型API设计。
虽然RESTful设计风格曝光率很高,但并不是所有云服务商都选择了完全遵循RESTful,例如AWS和 阿里云[3] RPC 风格反而占了大多数,Google和Azure则RESTful居多。
RESTful API的优势是HTTP具备更好的易用性,让异构系统更容易集成,且开发执行效率比较高,面向资源要求也比较高。而RPC API可以使用更广泛的框架和方案,技术层面更底层也更为灵活,设计起来相对简单,掌握起来有一定门槛,架构上更加复杂。RESTful 与 RPC 模式对比如下:
|
RESTful API |
RPC API |
---|---|---|
是否有统一规范 |
HTTP |
无 |
面向资源 |
是 |
不确定 |
性能 |
中 |
高 |
通用性 |
高 |
弱 |
复杂度 |
中 |
高 |
如果强制统一风格,有些适合 RESTful 风格的服务非要使用RPC的话,看起来就会比较丑陋,如果只是一个过程化的服务调用,往 RESTful 资源化设计方向去靠会比较困难。但如果不强制使用统一风格,会造成针对API的体系化支持会更加复杂,例如为兼容两种风格SDK的自动化支持需要两套代码。
选择API风格时要考虑几个问题:
- 选择支持哪种风格,才能更好地体现业务特性,让客户操作起来更加方便;
- 设计API时能否面向资源设计,相应的工程人员是否具备做这种设计的能力;
- 针对这种风格工具链的支持是否到位,投入产出比如何;
- 业界流行的趋势如何,是否需要考虑与其他系统体系的互操作。
用户使用API来访问 Service,本质上是想通过对某种资源执行特定的操作来完成一个业务动作。对于资源有两个关键点:一是要有统一的资源模型;二是要明确资源关系。统一的资源模型对 Service 的帮助是巨大的:
- 它可以使API具有更清晰的结构,帮助用户理解;
- 它可以帮助对比API与后台实体关系模型,更容易提供更完整的API服务;
- 它可以使产品协作更加顺畅,对资源的操作也更加规范化;
- 它可以使云服务底层平台实现起来更统一、更方便;
- 它可以使围绕API的生态集成起来更加简单、高效。
确定了设计模式和资源模型后,就需要考虑 API的设计细节了,诸如API名称、参数名、属性名称、数据格式、错误码之类的信息。除此之外,还要考虑以下一些问题:
- 在API命名的时候,遵循什么样的范式来确保大体风格相似?动词、名词、介词如何组合才能保持API风格看起来比较统一,降低理解成本?
- 对于类似的操作,有没有使用规范?有哪些公共的标准词汇使得同类型的操作可以比较容易理解,避免使用晦涩奇怪的词汇(例如读操作,Read/Query/Describe/List/Get中都在什么场合使用什么动词)?
- 被广泛使用的参数如何尽可能保持一致,避免不同产品的表达混乱的情况(例如分页参数用PageNumber还是PageNum)?
- 对于常用的场景,例如幂等、分页、异步API的设计有没有统一的规范,避免使用体验不一致?
- 错误码应该怎么设计?公共错误码怎么统一,业务错误码怎么表达?
上述问题都是实际研发过程中要注意的,要全部罗列的话远不止这些。API的用词描述是 Service 展现给外部用户的第一印象,绝非随意写就。对人员有一定规模,内部有多条产品线的组织来说,如何协调组织的各个部分对外具有统一的体验是个很大挑战。
Service 在管理API时应该考虑一些具体的规范,对命名规则、标准词汇、最佳实践模式、错误码等信息都有明确的规定,同时用系统化、平台化的手段来管理API,确保不走偏。设计风格不是云服务API设计中致命的问题,但是它关乎云服务外表形象,不可不察。
API是后端服务的外部表达,是服务就有可能出现问题,无论这个问题是可预期的还是不可预期的。如果只考虑功能本身功能特性,而忽视对异常情况的设计,当问题出现的时候业务本身可能无法感知造成服务异常,更重要的是站在客户角度去看,不能有效获取错误原因是非常痛苦的,很多时候只能束手无策,降低云服务提供商的整体口碑,甚至损害营收。
OpenAPI规范
本规范基于 RESTful 风格的架构设计准则,广泛参考 GitHub、Azure、Google API Design Guide、腾讯云、阿里云等公开资料,兼顾现有实际情况和未来发展做一个概括性记录总结。
一、协议
API 与用户的通信协议,总是使用 HTTPS 协议。这个和 RESTful API 本身没有很大的关系,但是对于增加网站的安全是非常重要的。特别如果你提供的是公开 API,用户的信息泄露或者被攻击会严重影响网站的信誉。
二、版本(Version)
关于版本的设计有3种形式:
-
将 API 的版本号放入 URL 中,如:
http://api.example.com/v1
,这样方便和直观; -
将版本号记录在 url query中,如:
http://api.example.com?param1=val&version=1.0
中的 version 参数。 -
将版本号放在 HTTP 头信息中,基于的准则是:不同的版本,可以理解成同一种资源的不同形式,所以应该采用同一个URL。如:
Accept: application/json; version=1.0
,可以参考 Github API Design[4] 和 Versioning REST Services[5] ;
根据现有的实际情况,如果是为了兼容已存在的服务接口,可以采用对应的形式。如果是新构建的体系结构,建议采用第三种。
三、Schema
URI的格式定义如下:
URI = scheme "://" authority "/" path \[ "?" query \] \[ "#" fragment \]
URL 是 URI 的一个子集(一种具体实现),对于 REST API 来说一个资源一般对应一个唯一的 URI(URL)。在 URL 的设计中,我们会遵循一些规则,使接口看起透明易读,方便使用者调用。
"/"分隔符一般用来对资源层级的划分。对于 RESTful API 来说,"/"只是一个分隔符,并无其他含义。为了避免混淆,"/"不应该出现在URL的末尾。
URL 中尽量使用连字符"-"代替下划线"_"的使用。
连字符"-"一般用来分割 URL 中出现的字符串(单词),来提高 URL 的可读性,例如:
http://api.example.restapi.org/blogs/mark-masse/entries/this-is-my-first-post
。使用下划线"_"来分割字符串(单词)可能会和链接的样式冲突重叠,而影响阅读性。但实际上,"-"和"_"对URL 中字符串的分割语意上还是有些差异的:"-"分割的字符串(单词)一般各自都具有独立的含义,可参见上面的例子。而"_"一般用于对一个整体含义的字符串做了层级的分割,方便阅读,例如你想在 URL 中体现一个 IP 地址的信息:210_110_25_88 . (欢迎关注:朱小厮的博客)
URL应该统一使用小写字母 。
URL中不要包含文件(脚本)的扩展名
。例如
.json
之内的就不要出现了,对于接口来说没有任何实际的意义。如果是想对返回的数据内容格式标示的话,通过 HTTP Header 中的 Content-Type 字段更好一些。
对于响应返回的格式,JSON 因为它的可读性、紧凑性以及多种语言支持等优点,成为了 HTTP API 最常用的返回格式。因此,最好采用 JSON 作为返回内容的格式。如果用户需要其他格式,比如
xml
,应该在请求头部
Accept
中指定。对于不支持的格式,服务端需要返回正确的
status code
,并给出详细的说明。
JSON中的所有字段都应该用小写的蛇形命名形式,而不是采用驼峰命名。
四、以资源为中心的 URL 设计
资源是
Restful API
的核心元素,所有的操作都是针对特定资源进行的。而资源就是
URL
(Uniform Resoure Locator)表示的,所以简洁、清晰、结构化的 URL 设计是至关重要的。Github 可以说是这方面的典范,下面我们就拿
repository
来说明:
/users/:username/repos
/users/:org/repos
/repos/:owner/:repo
/repos/:owner/:repo/tags
/repos/:owner/:repo/branches/:branch
我们可以看到几个特性:
- 资源分为单个文档和集合,尽量使用复数来表示资源,单个资源通过添加 id 或者 name 等来表示
- 一个资源可以有多个不同的 URL
- 资源可以嵌套,通过类似目录路径的方式来表示,以体现它们之间的关系
最常见的一种设计错误,就是URL包含动词。
因为"资源"表示一种实体,所以应该是名词,URL 不应该有动词,动词应该放在 HTTP Method (参考下一条)中。举例来说,某个 URL 是
/users/show/1
,其中
show
是动词,这个 URL 就设计错了,正确的写法应该是
/users/1
,然后用 HTTP GET 方法表示 show。
如果某些动作是HTTP 动词表示不了的,你可以把动作看成是一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的 URL 是:
POST /accounts/1/transfer/500/to/2
正确的写法是把动词 transfer 改成transaction,资源不能是动词,但是可以是一种服务:
POST /transactoin HTTP/1.1
HOST: 127.0.0.1
from=1&to=2&amount=500
五、正确使用 HTTP Method
有了资源的 URI 设计,所有针对资源的操作都是使用 HTTP 方法指定的。比较常用的 HTTP/1.1 动词有下面5个:
- GET:从服务器取出资源(一项或多项)。
- POST:在服务器新建一个资源。
- PUT:在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH:在服务器更新资源(更新资源的部分属性)。
- DELETE:从服务器删除资源。
还有4个不常用的 HTTP/1.1 动词:
- HEAD:只获取某个资源的头部信息。比如只想了解某个文件的大小,某个资源的修改日期等
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
-
TRACE:追踪路径。不建议使用。 -
CONNECT:要求用隧道协议连接代理。不建议使用。
举例:
GET /repos/:owner/:repo/issues
GET /repos/:owner/:repo/issues/:number
POST /repos/:owner/:repo/issues