1、前言
作为开发人员,一定会和API打交道,大家平时也一定设计和开发自己的API。以前,API是作为前端和后端通讯的桥梁,随着微服务的兴起,API更是起到了各个子系统的粘合剂的作用。
优秀的 API 之于代码,就如良好语言对于每个人。优秀的 API设计不但利于使用者理解,开发时也会事半功倍,后期维护更是方便快捷。
API的设计千差万别,很难有处处适用的准则规范,所以在讨论原则和最佳实践时,无论这些原则和最佳实践是什么,一定有适应的场景和不适应的场景。因此我们在此讨论的是,尽可能的适用于我们的约定,而非强制规则!
2、域名
域名应该尽量将API部署在专用域名之下:
https://api.myun.info
如果在前后端不分离的情况下,可以考虑放在主域名下:
https://h5.myun.info/api/
3、版本号
API 接口应该尽量兼容之前的版本。但是,在实际业务开发场景中,可能随着业务需求的不断迭代,现有的 API 接口无法支持旧版本的适配,此时如果强制升级服务端的 API 接口将导致客户端旧有功能出现故障。
为了解决这个版本不兼容问题,在设计API的一种实用的做法是使用版本号。一般情况下,我们会在 url 中保留版本号,并同时兼容多个版本。
https://api.myun.info/v1/users/get-users-listhttps://api.myun.info/v2/users/get-users-list
现在,我们可以在不改变版本 v1 的查询用户列表的 API 接口的情况下,新增版本 v2 的查询用户列表的 API 接口以满足新的业务需求,此时,客户端的产品的新功能将请求新的服务端的 API 接口地址。
虽然服务端会同时兼容多个版本,但是同时维护太多版本对于服务端而言是个不小的负担,因为服务端要维护多套代码。这种情况下,常见的做法不是维护所有的兼容版本,而是只维护最新的几个兼容版本,例如维护最新的三个兼容版本。
在一段时间后,当绝大多数用户升级到较新的版本后,废弃一些使用量较少的服务端的老版本API 接口版本,并要求使用产品的非常旧的版本的用户强制升级。
需要注意的是,“不改变版本 v1 的查询用户列表的 API接口”主要指的是对于客户端的调用者而言它看起来是没有改变。而实际上,如果业务变化太大,服务端的开发人员需要对旧版本的 API 接口使用重定向将请求重定向到新的API接口上。
4、以资源为中心的URL设计
以资源为中心设计URL,而非以模块来命名URL。
虽然我们的API并非严格意义的RestFul API,但依旧应该以资源为中心,毕竟外部通过API想获取的是资源,而并不关心你内部的模块结构。
URL是资源的集合,应该使用复数,并且只包含名词(某些情况下需要形容词):
https://api.myun.info/v1/usershttps://api.myun.info/v1/tagshttps://api.myun.info/v1/overdue-goods
方法是对资源的具体操作
我们的请求统一使用的是Post方法,并未采用RestFul模式,因此URL最后部分是具体的方法名,应以是一个动 作:
https://api.myun.info/v1/users/get-user-listhttps://api.myun.info/v1/tags/add-tag
- 大小写
URL是大小敏感的,建议全部使用小写,用”-“作为间隔符!之所以不采用驼峰命名,是因为在命名比较长或者使
用缩写时,不利于使用者理解,也容易导致书写错误。
5、参数
考虑到很多后端直接映射数据库,在此不对参数命名做强制要求,但至少要做到语义明确。
- 请求参数:
请求端在输入请求参数时,请遵循所写既所用的原则,不要传入一些无用的和废弃的参数,从而导致误解。
接受端在接收到请求参数时,需对参数的合法性做一定的校验,对于非必传参数,需合理的设置默认值,从而避
免一些因参数不正确导致的异常问题。
- 响应参数:
返回的响应参数,请遵循所求既所得的原则,不要返回一些无用参数,从而导致增加传输负担和降低解析效率。
我们以前设置API时,总是寄希望于制造一个银弹,一个API在各个子系统和各个端中横行天下。这种方式虽然某种程度上减少了一点开发量,但是牺牲了很大的性能,也降低了后期的可维护性和扩展性。 合理的方式应该是,一个业务的接口I在不同的应用场景中应该独立分开成多个API。
6、合适的状态码
HTTP应答中,需要带一个很重要的字段:status code。它说明了请求的大致处理情况,是否正常完成、是否需要进一步处理、出现了什么错误,这对于客户端非常重要。状态码都是三位的整数,大概分成了几个区间:
- 2XX:请求正常处理并返回
- 3XX:重定向,请求的资源位置发生变化
- 4XX:客户端发送的请求有错误
- 5XX:服务器端错误
在 HTTP API 设计中,经常用到的状态码以及它们的意义如下表:
| 状态码 | LABEL | 解释 |
|---|---|---|
| 200 | OK | 请求成功接收并处理,一般响应中都会有 body |
| 201 | Created | 请求已完成,并导致了一个或者多个资源被创建,最常用在 POST 创建资源的时候 |
| 202 | Accepted | 请求已经接收并开始处理,但是处理还没有完成。一般用在异步处理的情况,响应 body 中应该告诉客户端去哪里查看任务的状态 |
| 204 | No Content | 请求已经处理完成,但是没有信息要返回,经常用在 PUT 更新资源的时候(客户端提供资源的所有属性,因此不需要服务端返回)。如果有重要的 metadata,可以放到头部返回 |
| 301 | Moved Permanently | 请求的资源已经永久性地移动到另外一个地方,后续所有的请求都应该直接访问新地址。服务端会把新地址写在 Location头部字段,方便客户端使用。允许客户端把 POST 请求修改为 GET。 |
| 304 | Not Modified | 请求的资源和之前的版本一样,没有发生改变。用来缓存资源,和条件性请求(conditional request)一起出现 |
| 307 | Temporary Redirect | 目标资源暂时性地移动到新的地址,客户端需要去新地址进行操作,但是不能修改请求的方法。 |
| 308 | Permanent Redirect | 和 301 类似,除了客户端不能修改原请求的方法 |
| 400 | Bad Request | 客户端发送的请求有错误(请求语法错误,body 数据格式有误,body 缺少必须的字段等),导致服务端无法处理 |
| 401 | Unauthorized | 请求的资源需要认证,客户端没有提供认证信息或者认证信息不正确 |
| 403 | Forbidden | 服务器端接收到并理解客户端的请求,但是客户端的权限不足。比如,普通用户想操作只有管理员才有权限的资源。 |
| 404 | Not Found | 客户端要访问的资源不存在,链接失效或者客户端伪造 URL 的时候回遇到这个情况 |
| 405 | Method Not Allowed | 服务端接收到了请求,而且要访问的资源也存在,但是不支持对应的方法。服务端必须返回 Allow头部,告诉客户端哪些方法是允许的 |
| 415 | Unsupported Media Type | 服务端不支持客户端请求的资源格式,一般是因为客户端在 Content-Type或者 Content-Encoding中申明了希望的返回格式,但是服务端没有实现。比如,客户端希望收到 xml返回,但是服务端支持 Json |
| 429 | Too Many Requests | 客户端在规定的时间里发送了太多请求,在进行限流的时候会用到 |
| 500 | Internal Server Error | 服务器内部错误,导致无法完成请求的内容 |
| 502 | Bad GateWay | 服务器作为网关或代理,从上游服务器收到无效响应。 |
| 503 | Service Unavailable | 服务器因为负载过高或者维护,暂时无法提供服务。服务器端应该返回 Retry-After头 |
| 504 | Gateway timeout | 服务器作为网关或代理,但是没有及时从上游服务器收到请求 |
| 505 | HTTP Version Not Supported | 不支持的 HTTP 版本 |
7、错误处理
除了使用http自带的status code,来处理请求的系统级错误外,我们同样需要在响应参数中加入code和message,来处理一些业务级别的错误。
目前我们常用IsSuccess只是简单的bool类型,无法明确具体的业务错误
8、安全性
8.1、Https
所有的访问API行为,都需要用 TLS 通过安全连接来访问。没有必要搞清或解释什么情况需要 TLS 什么情况不需要 TLS,直接强制任何访问都要通过 TLS。
理想状态下,通过拒绝所有非 TLS 请求,不响应 http 或80端口的请求以避免任何不安全的数据交换。如果现实情况中无法这样做,可以返回403 Forbidden响应。
8.2、签名
针对所有请求,请求端需根据请求参数生成签名,接收端对签名进行验证,具体方式有两种:
1、对所有请求参数做一次json stringfiy,然后对string做md5加密
2、对说有请求参数做acs排序,然后对生成的字符串做md5加密
两种方式可分别参考我们的模板工程:easy-front-express-api和easy-front-open-api-server
8.3、限流
限流一般针对对外的open api,内部api因为效率问题不推荐使用。
限流主要是对访问的次数加以控制,如果不控制很可能会造成 API 被滥用,甚至被DDos。根据使用者不同的身份对其进行限流,可以防止这些情况,减少服务器的压力。
具体可以参考我们的模板工程:easy-front-open-api-server
8.4、其他
另外基于安全的措施还有AppKey和TimeStamp
- AppKey:只允许合法的第三方请求API
- TimeStamp:防止请求回放
9、如何编写API文档
API 最终是给人使用的,即使我们遵循了上面提到的所有规范,设计的API 非常优雅,如果没有优秀的文档,用户还是不知道怎么使用我们的API。因此最后还有非常重要的一步:为API 编写优秀的文档。
一般API文档需包含以下几个公共部分:
- 版本号:描述文档每次修改针对的API版本号,并清楚描述修改内容、修改人、修改时间
- 协议说明:描述API整体的请求方式、使用的协议、数据的格式
- 请求域名:描述生产、测试、验收等环境的域名地址
- 公共请求参数和响应参数:描述所有请求和响应都会有的参数,比如timestamp、sign
- 返回码code:描述code各个值所代表的具体含义
- 签名和验签:描述签名和验签的具体规则方法
一般每个API需要描述清楚以下几个部分:
- 场景:描述使用API的业务场景
- 发起方:描述谁请求这个API
- 接受方:描述谁处理这个API
- 请求方式:Post还是Get、PUT
- URL:具体的请求路径
- 请求参数:包含参数名、参数类型、是否必须、参数说明
- 响应参数:包含参数名、参数类型、是否必须、参数说明
- 示例:具体的请求和响应的数据示例
