首页  ·  知识 ·  架构设计
微服务应用API设计规范
CIO之家的朋友  jaggerwang  综合  编辑:提刀杀红眼   图片来源:网络
本规范是笔者为所带领的研发团队制定的API设计规范,从请求方式、请求路径、请求参数、请求体、响应状态码、响应体等方面对API的设计进行了一些约定,以便统一各API的风格。

本规范是笔者为所带领的研发团队制定的 API 设计规范,从请求方式、请求路径、请求参数、请求体、响应状态码、响应体等方面对 API 的设计进行了一些约定,以便统一各 API 的风格。

命名规范

如无特别说明,路径、参数、变量等命名统一采用首字母小写的驼峰方式。

字符编码

如无特别说明,字符编码统一采用 UTF-8。

URL 规范

请求方式

  1. GET 读取

  2. POST 创建,非幂等

  3. PUT 整体替换,幂等

  4. PATCH 部分更新

  5. DELETE 删除

为了简化沟通和理解,如非必要请勿使用 PUT、PATCH 和 DELETE,这三种请求方式均可使用 POST 结合路径中的操作来实现,具体可参考下面的“路径和参数”章节。

路径和参数

围绕资源来操作

GET /user/list 列表查询
GET /user/info/1 ID 查询,如需查询多个,ID 列表通过 Query 参数传递,形如 ids=1,2,3
POST /user/create 创建
POST /user/update/1 更新,只更新传递的字段,未传递的字段保持不变
POST /user/replace/1 替换,整体更新,未传递的字段将清空
POST /user/delete/1 删除,如需删除多个,ID 列表通过请求体传递,形如 {"ids": [1, 2, 3]}

Plain

Copy

资源 ID 参数通过路径参数来传递。考虑到有些业务操作不是针对资源,比如发送通知或邮件,路径前缀不要使用 users 这样的复数形式,建议使用模块名。

非资源 ID 参数放到 Query 里

GET /user/list?gender=male&minAge=18
GET /user/infoByUsername?username=jack
GET /user/follow?followingId=1&followerId=2 涉及到多个 ID 组合才能定位资源时,统一通过 Query 参数传递

Plain

Copy

对于 POST 请求不要使用 Query 参数,非资源 ID 参数只能通过请求体传递。

业务操作放到路径里

POST /user/sendVerifyCode

Plain

Copy

API 路径

对于采用微服务架构的应用,需要通过路径前缀来区分前后端资源,以及不同的微服务。

  1. 所有微服务的 API 统一到 /api 路径前缀下,可在反向代理层添加该前缀,在传递给后端服务时抹去该前缀,以便该前缀对后端服务来说无感知;

  2. 在应用网关层为各微服务添加相应的路径前缀(比如用户微服务 /user),该前缀同样对各微服务来说无感知;

  3. 在微服务内部按模块划分 API,比如用户微服务的用户模块的创建用户 API /user/create(对外暴露的完整路径为 /api/user/user/create,这里服务名和模块名同名);

API 版本

对于内部使用的 API,自己可以控制客户端升级节奏,应避免使用 API 版本,因为维护多版本 API 的成本很高。对于对外开放的 API,由于无法控制使用方客户端升级节奏,那么可以通过多版本 API 来实现平滑升级,在废弃老版 API 之前给使用方留足够的升级时间。 API 版本号可在不同的层级上添加,以前面的创建用户 API /api/user/user/create 为例,按作用范围由大到小有以下几种方式:

  1. /api/v1/user/user/create 在应用网关层级添加(推荐);

  2. /api/user/v1/user/create 在微服务层级添加;

  3. /api/user/user/v1/create 在模块层级添加;

  4. /api/user/user/create/v1 在 API 层级添加;

不建议为不同版本的服务启动不同的实例,随着版本的不断升级,后期的维护工作会越来越大。对于同一个服务的不同版本 API,应使用一套代码,新版 API Controller 可以通过继承老版 Controller 来尽量复用现有代码。

请求

公共参数

通过 HTTP 头来传递公共参数,优先使用 HTTP 标准头,没有合适的再自定义,自定义 HTTP 头需以 X- 打头。

HTTP 头

示例

用途

Authorization

Bearer <token>

认证服务颁发的 Token

Accept-Language

zh-CN,zh;q=0.9,en-US,en;q=0.1

可接受的响应内容语言,优先使用权重高的

X-Client-Name

BasicAI

客户端名称

X-Client-Version

v1.0.0

客户端版本

请求体

  1. 请求体采用 JSON 格式,且为一个 JSON 对象;

  2. 请求体为单个值时需包装为一个对象,以保持最外层结构统一;

POST /user/create


{
 "username": "jack",
 "password": "12345678",
 "age": 17}


响应

响应状态码

HTTP 状态码设计的初衷是用于静态资源访问场景,对于 API 这种动态服务场景,许多状态码都不适用,或者根本无法表示各种千奇八怪的业务错误。因此这里推荐只使用下面这些少量的 HTTP 状态码,其它业务异常情况统一响应 200 状态码,并在响应体里通过 code 返回具体的业务错误码。

  • 200 OK 操作成功

  • 400 Bad Request 一般性客户端错误

  • 401 Unauthorized 未认证

  • 403 Forbidden 未授权

  • 404 Not Found 资源未找到

  • 405 Method Not Allowed 请求方式不支持

  • 429 Too Many Requests 请求太频繁

  • 500 Internal Server Error 服务器内部错误

  • 502 Bad Gateway 网关请求上游服务时出错

  • 503 Service Unavailable 服务不可用,比如重启或维护中

  • 504 Gateway Timeout 网关请求上游服务超时

响应体

响应体为 JSON 对象,结构如下:

{
 "code": "OK", // 业务错误码  "message": "", // 业务错误描述  "data": null // 业务数据}

JSON

Copy

  1. 业务错误码采用常量字符串格式(只能使用大写字母、下划线和数字),形如 USER__USER__USERNAME_DUPLICATED,其中前两级依次为服务和模块,一些与服务和模块无关的公共错误码没有前缀;

  2. 业务错误描述可直接展示给用户(但不推荐),请勿包含任何涉密信息,考虑到国际化,请使用英文;

  3. 业务数据如果只有单项那么直接通过 data 字段返回,这样可以让后端省去大量的包装类定义,如果有多项那么只能再包一层,在 data 下通过不同字段区分,如果没有则为 null

  4. 只有在 HTTP 响应状态码为 200 时才保证响应体符合标准格式;

示例

单项业务数据直接通过 data 字段返回:

{
 "code": "OK",
 "message": "",
 "data": {
   "id": 1,
   "username": "jack"
 }}

JSON

Copy

多项业务数据需包一层:

{
 "code": "OK",
 "message": "",
 "data": {
   "user": {
     "id": 1,
     "username": "jack"
   },
   "roles": []
 }}

JSON

Copy

列表数据:

{
 "code": "OK",
 "message": "",
 "data": {
   "users": [],
   "total": 1000
 }}

JSON

Copy

空列表如果属于正常情况,那么请返回空数组而不是 null,这样可以避免调用方去做 null 判断。 业务出错:

{
 "code": "BILLING__PAY__MONEY_NOT_ENOUGH",
 "message": "money not enough",
 "data": null}

JSON

Copy

分页规范

基于页

适合客户端有翻页条,按页展示数据,数据集变化较慢的场景。

  1. 分别使用 totalpageNopageSizelist 来传递和返回总数(可选)、当前页码、每页条数和当页数据;

  2. 冗余返回当前页码和每页条数,以便客户端依据响应结果即可知道如何请求下页数据;

{
 "code": "ok",
 "message": "",
 "data": {
   "total": 100,
   "pageNo": 1,
   "pageSize": 10,
   "list": []
 }}

JSON

Copy

基于偏移量

适合没有翻页条的流式加载模式场景,如果起始位置使用主键 ID、创建时间这样的排序字段,服务端可以通过大于或等于比较来加速查询,并且在数据集有变化时能够保证分批获取的数据不重复。

  1. 分别使用 totaloffsetlimitlist  来传递和返回总数(可选)、起始位置、返回条数和当页数据;

  2. 起始位置除了是位置序号,还可以是任意有序字段的值,比如创建时间;

  3. 冗余返回起始位置和返回条数,以便客户端依据响应结果即可知道如何请求下页数据;

{
 "code": "ok",
 "message": "",
 "data": {
   "total": 100,
   "offset": 0,
   "limit": 10,
   "list": []
 }}


本文作者:CIO之家的朋友 来源:jaggerwang
CIO之家 www.ciozj.com 微信公众号:imciow
    >>频道首页  >>网站首页   纠错  >>投诉
版权声明:CIO之家尊重行业规范,每篇文章都注明有明确的作者和来源;CIO之家的原创文章,请转载时务必注明文章作者和来源;
延伸阅读