23.4 API的设计原则和规范
API是服务提供方和使用方之间对接的通道,前面我们设计的一些简单API的例子,基本上比较随意,没有使用任何规范。设想一下,每个平台都可能存在大量的API,如果API设计没有原则,也没有统一的规范,按开发者的意愿随意编写,访问千差万别的API,不仅让API的使用非常麻烦,对API的改动也会导致项目或移动App无法工作。当然,一个好的规范对于解决这些事情能起到事半功倍的作用。如果想让服务端的价值更好地体现出来,就要好好设计API。通过使用规范的API,我们的服务或核心程序将有可能成为其他项目所依赖的平台。我们提供的API越易用,就会有越多人愿意使用它。
23.4.1 什么是RESTful风格的API
现在不管是开发移动应用,还是基于前后端分离,企业在设计API时都会遵循RESTful风格。REST(Representational State Transfer)定义了一套基于Web的数据交互方式的设计风格,符合REST风格的API就可以叫作RESTful API,REST只是风格没有标准,是目前最流行的一种互联网软件架构。它结构清晰、符合标准、扩展方便,基于这个风格设计的软件可以更简洁、更有层次、更易于实现缓存等机制,所以越来越多的网站和项目开始采用RESTful风格。Web应用程序的API设计,最重要的REST原则是,客户端和服务器之间的交互请求之间是无状态的。从客户端到服务器的每个请求,都必须包含理解请求所必要的信息。
23.4.2 RESTful API应遵循的原则
一次完整的API调用过程,所涉及的每个环节都需要设计为统一的风格,包括客户端使用的HTTP方法、URI的格式、返回数据的结构等,都应遵循RESTful API的原则。
1. 协议
API与用户的通信协议,尽量使用HTTPs协议。使用HTTPs协议和RESTful API本身没有多大关系,但是对于增加网站的安全性是非常重要的,特别是如果提供的是公开的API,那么HTTPs就更显得重要了。因为HTTPs协议的所有信息都是加密传播的,第三方无法窃听,具有校验机制,一旦被篡改,通信双方会立刻发现,配备身份证书,防止身份被冒充。如果做不到全部都使用HTTPs协议,也一定要在项目的登录和注册的接口上使用。因为在这两个接口用户还没有登录系统,没有获取用户身份,是最容易被窃听的。其他登录后才需要访问的API可以使用HTTP协议。
2. 域名
请求API的URL中除了协议外, 请求的根地址也很重要,一个好的软件架构,最好要让API的系统可以有单独的访问通道。应该尽量将API部署在专用的域名下面,例如:
https://api.ydma.com
如果确定API很简单,不会有进一步的扩展,可以考虑把API放在子域名下面,例如:
https://ydma.com/api/
3. 版本
在设计API时一定要有版本规划,以便以后API的升级和维护。应强制性规划API版本,不要发布无版本的API。在使用简单数字时,避免有小数点,如v3.5。应该将API的版本号放入URL中,如下所示:
https://api.ydma.com/v1/
当然,也可以将版本号放在HTTP的头信息中, 但不如放入URL方便和直观。
4. 路径
路径表示API的具体网址URL,在RESTful架构中,每个网址代表一种“资源”的存放地址,所以网址中不能有动词,只能有名词,而且名词一般都应该与数据库的表和字段对应且使用复数,例如:
https://api.ydma.com/v1/courses // IT云课堂的所有课程,代表多个资源
https://api.ydma.com/v1/courses/1/php // id为1的课程中的所有PHP课程,代表多个资源
https://api.ydma.com/v1/courses/1 // 代表单个资源,id为1的课程
https://api.ydma.com/v1/courses/1;2;3 // 代表单个资源,id为1、2、3的课程
URL中“/”表示层级,用于按“资源实体”的关联关系进行对象导航,一般跟进id导航。过深的导航层级容易导致URL膨胀,不易维护,如https://api.ydma.com/v1/ courses/1/web/3/php/4,尽量使用查询参数代替路径中的实体导航。
注意:根据RFC3986定义,URL是区分大小写的,所以应该尽量使用小写字母来命名!
5. 请求方法
有了“资源”的URL设计,所有针对资源的操作,都用指定的HTTP动词,HTTP常见的操作动词如表23-2所示。
表23-2 HTTP常见的操作动词
注意:GET方法和查询参数不应该涉及状态改变。应使用PUT、POST和DELETE方法而不是GET方法来改变状态。PUT更新单个资源(全量),由客户端提供完整的更新后的资源。与之对应的是 PATCH,PATCH 负责部分更新,由客户端提供要更新的字段。
6. 过滤信息
如果数据量太大,服务器不可能将所有数据返给用户。API应该提供参数来过滤返回结果,为集合提供过滤、排序、选择和分页等功能。如表23-3所示,是URL中常见的过滤信息。
表23-3 URL中常见的过滤信息
另外,在移动端开始时,由于移动设备的显示空间小,只能显示其中一些必要的字段,其实不需要接口返回一个资源的所有字段,给API一个选择字段的功能,这会降低网络流量,提高API的可用性,例如:
GET https://api.ydma.com/v1/users?fields=id,username,sex&state=open&sort=age // 获取三个字段
7. 固定返回码
在HTTP头部报文构成中,字段“status code”很重要。它说明了请求的大致情况,是否正常处理、出现了什么错误等。状态码都是3位数,大概分为以下几个区间,如表23-4所示。
表23-4 HTTP 状态码区间说明
我们在使用RESTful API时需要使用返回码的原因大致如下,客户端在调用一个API之后,接收到的反馈必须能够标识这次调用是否成功,如果不成功,客户端需要得到执行失败的原因。我们可以在API设计时做一个小小的约定,就能完美的满足自己的需求了。REST的大部分实现都是基于HTTP的,那么自然而然就少不了与返回码打交道。遗憾的是,HTTP的返回码的定义很多,也很烦琐,信息表达还不够详细,和实际开发中用到的状态含义还有些差距。虽然很多公司的开发组都直接使用HTTP的状态码,但笔者建议,根据项目自身去自定义一些状态码和对应的错误返回信息会更适用一些,如表23-5所示。
表23-5 自定义状态码参考
尽可能提供准确的错误信息,如数据格式不正确、缺少某个字段等,而不是直接返回“请求错误”之类的信息。
8. 固定返回结构
现在,越来越多的API设计会使用JSON来传递数据,本章也以JSON为例。只要调用成功,服务器端必须响应数据,而响应数据的格式在任何情况下都应当是一致的,这样有利于客户端处理返回结果。自定义服务器端所有的响应格式如下:
成功返回的消息结构
失败返回的消息结构
{
"code": 0,
"message": "",
"time":1527158707,
"data":{"id":"1","username":"高洛峰","sex":"男","age":"30"}
}
{
"code": E002,
"message": "无效的请求",
"time":1527158402,
"data":{ }
}
它们的含义如下:
Ø code为0代表调用成功,其他是自定义的错误码。
Ø message表示在API调用失败的情况下详细的错误信息,这个信息可以由客户端直接呈现给用户,否则为空。
Ø time 返回的是服务器动态时间,通过这个时间的变化,让用户知道重新请求过服务器。
Ø data表示服务器端返回的数据,具体格式由服务器端自定义,API调用错误则为空。
REST风格的API会针对不同操作,服务器向用户返回的不同结构的结果,需要符合以下规范,但笔者认为这样比较烦琐,如下所示:
GET https://api.ydma.com/v1/collection
GET https://api.ydma.com/v1/collection/resource
POST https://api.ydma.com/v1/collection
PUT https://api.ydma.com/v1/collection/resource
PATCH https://api.ydma.com/v1/collection/resource
DELETE https://api.ydma.com/v1/collection/resource
#返回一个资源对象的列表
#返回一个资源对象
#返回新创建的资源对象
#返回一个完整的资源对象
#返回一个完整的资源
#返回一个空文档
9. 编写文档
API最终是让人使用的,无论是对内还是对外,即使遵循上面提到的所有规则,API设计得很优雅,有时候用户还是不知道该如何使用它们。因此,编写清晰可读的文档是很有必要的。另外,编写文档也可以作为产出的一部分,以及用来记录,以方便查询参考。项目组中常用的API文档结构如图23-10所示。
图23-10 项目组中常用的API文档结构
当然,如果单独管理API文档是比较麻烦的,我们可以借助工具来管理,如国内比较不错的平台www.showdoc.cc,可以在该平台上注册一个账号管理接口文档,也可以独立搭建网站来管理,下载地址 https://www.getpostman.com/。该工具不仅可以管理文档,还可以模拟HTTP请求,当作应用接口的客户端来使用,操作比较简单,还可以模拟GET、POST等请求接口,也能查看响应结果,针对 JSON 格式的数据还提供解析功能。