Skip to content

分布式API设计 下:REST

About 2813 wordsAbout 9 min

架构

2025-02-18

2000年,互联网兴起数年,企业系统广泛流行通过HTTP 实现远程调用,代替EJB,当时基于HTTP远程调用,无论URL的设计,参数命名,以及响应都没有统一的规范。Roy Fielding 针对此问题, 提出了表示状态转移(Representational State Transfer),简称REST,作为一种设计web服务的体系结构方法 .REST 是目前最流行的HTTP API 设计规范,用于 Web 接口的设计 。

REST的核心思想就是,客户端发出的指令都是"标准动词 + 操作资源"的结构。比如,GET /orders/1这个命令,GET是动词,/orders/1 是宾语,这里的1是指订单编号为1的订单。

如下是一个REST请求

GET https://xxxx/orders/1 HTTP/1.1
Accept: application/json

响应

HTTP/1.1 200 OK

{
"code":"SUCCESS"
"messsage":"成功"
"data":{"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
}

这里的标准动词是HTTP规范提供的标准,如上面的get,其他动作还有post,delete,将会在后面描述。

相比于HTTP 远程调用,REST是以资源作中心构建的远程调用服务,这里的资源指的是业务实体,业务实体来自于数据架构。一个良好的REST API设计,离不开良好的数据架构

以GIT 提供的服务为例子,资源包括了

  • 仓库
  • 用户
  • Issues
  • Release
  • Team

以物联网为例子,资源包括了

  • 设备
  • 用户
  • 家庭
  • 属性
  • 告警
  • OTA
  • 命令

在上一节,比较过二进制协议和HTTP的区别,建立在HTTP之上的REST,具备HTTP的优点

  • 语言独立: 所有语言都支持HTTP协议,也提供REST风格的客户端接口
  • 平台独立: 所有的系统都支持HTTP
  • 可观测性强: 通过HTTP代理服务器,以及良好的REST接口定义,可以让系统之间调用可观测,可统计

基于HTTP协议的除了REST外,还有非常流行的SOAP,简单区分俩种的区别

  • REST更加轻量级,SOAP更加重量级,SOAP更像是传统分布式调用体系的的HTTP版本,适合传统企业应用,适合系统之间调用。比如SOAP严格定义了自己的安全机制,寻址方式,以及协议格式。而REST则不受限制。
  • REST以资源为中心,SOAP通常是以服务为中心。

本节介绍REST的设计最佳实践,它除了上一节的分布式API通用的最佳实践外,REST API 还有 特定的设计原则,总结为下表内容和表后的重点说明

设计原则描述
URLURL必须定义了资源,比如/books/1978
命名规范驼峰命名方法,蛇形命名法,烤肉串命名法。比如对于显示名称 display name。分别对应了displayName,display_name, dispaly-name。 REST的URL通常使用烤肉串命名法,入参和出参数使用Java常用的驼峰命名法
资源名称API的核心是操作是WEB资源或者业务实体,因此需要在这之前定义好这些名称,这些名称在数据架构或者更早的的数据架构前已经定好。比如用户中心提供的REST接口查询用户的绑定设备GET /users/devices 。如果缺少标准名称,另外一个接口查询设备状态 GET /devs/customer . 这让API使用者有困惑,他可能认为devices和devs 可能是物联网的俩种设备资源名称通常以复数形式体现
资源层级当资源必须通过层级获取的时候,可以用/表达层级,比如/orders/{orderId}/items, 建议层级不超过3层。
标准字段定义常见的标准字段,如requestId,token,createTime,orderBy,displayName,startDate,endDate等. 其他行业特定的字段应该基于数据架构的概念模型来定义,比如物联网中的device,shadow, 电商中的coupon,order,sku
开闭区间表示范围,需要统一规定是否包含临界值,Java通常使用[、),起始值闭区间,结束值为开区间,推荐使用这种约定
使用单位后缀比如优先使用maxByte代替max,除非有上下文指示返回的单位。 关于时间,精确到秒,应该使用time,精确到天,应该使用date后缀。
名字缩写软件开发者常用的缩写可以用在API上,如id,config,电商中的Sku。
保留字class是java的关键字,因此在设计API的时候需要避免参数中使用此关键字,这主要考虑到序列化和反序列化时候,这类关键字无法被实现。
版本号通常使用version表示api的版本,比如/gms/v1.0/devices,建议REST URL 总是包含version。另外可选的是在HTTP Header中包含version。比如github api推荐把version放到header里

下表列出了 Github REST API

REST接口说明
/user/repos获取当前用户的所有仓库
/repos/{owner}/查询一个仓库信息
/repos/{owner}/{repo}/releases来自github api,返回代码库的所有release
/repos/{owner}/{repo}/releases/来自github api,返回代码库的指定的release 信息

下列情况是错误的URL例子

REST接口正确错误分析
/book/books通常使用复数,除非查询单个对象
/user/create/userURL 不应该包含动词
/users/{id}/books.json/users/{id}/books无需指定返回的格式,通常默认是json,如果需要其他格式,可以通过媒体类型来指定,比如application/xml

思考:查看你系统的的REST API,看看有没有不符合RESTAPI 设计原则的

标准方法命名

HTTP提供了标准的动作 GET,PUT,POST,DELETE等,

  • GET:读取
  • POST:新建
  • PUT:创建或者更新
  • PATCH:更新(Update),通常是部分更新
  • DELETE:删除(Delete)
URL动作含义
/usersGET查询所有用户
/users/1GET查询id为1的用户
/usersPOST创建新的用户
/users/1PUT创建或者更新用户,id为1
/users/1DELETE删除用户1
/usersDELETE删除所有用户

范围查询

规定使用offset和limt作为查询参数,如果数据来源是大数据,通常使用cusor方式查询,翻页范围使用cusor和limit参数俩个,这里的cusor通常是递增主键id

HTTP Stauts

在开发API时候,对于REST的响应结果,还需要进一步规范如下API说明,通常用如下

  • 200 表示成功
  • 400 表示客户端错误
    • 400 服务器不理解请求,如参数错误
    • 401 未通过身份验证
    • 403 未授权的访问资源
    • 404 访问资源不存在
  • 500 表示服务端错误

关于HTTP Status 定义,参考HTTP 响应状态

如果这些Status Code不足以表达更明确的含义,建议在响应体的JSON里包含code 进一步明确错误含义,比如code为PARAMETER_ERROR 表示参数校验错误。message通常用于补充code含义

HTTP/1.1 200 OK
Content-Type: application/json

{
"code":"SUCCESS"
"messsage":"库存查询成功"
"data":{
   
}
}

错误响应: 对于基于HTTP协议的API,可以充分利用HTTP STATUS来表示API是否成功,STATUS 304 表示授权错,STATUS 500 表示内部失败,等等,使用HTTP STATUS也有利于其他系统,如网关或者监控系统对错误进行观测。

HTTP/1.1 500 ?
Content-Type: application/json

{
"code":"PARAMETER_ERROR"
"messsage":"操作失败,"
"data":{
   
}
}

使用HTTP Status,还是使用响应的Body中的code来指示REST的响应结果,屈居于俩个因素

1) HTTP Status仍然能粗略的表达REST 响应状态,屈居于客户端是否对状态更详细的要求,比如HTTP 500表示服务器内不错误,对于大多数电商和企业应用系统,是不足以表达的

1) 可观测性,如果已经搭建好的有可观测系统,且REST返回结果能被客观性系统收集到,则可以使用CODE来指示结果。 如果此时搭建的有NGINX 的日志监控,HTTP Status也能粗略的统计到REST 请求结果

如果API实现异步调用,需要通过文档向调用者说明,从哪里可以获取异步执行结果。或者提供一个新的API,用户可以通过reqeust_id,查询异步调用结果。或者服务端返回一个查询ID,用户可以用此ID查询结果

或者对于REST请求,也可以在响应中包含一个URL,供用户查询。如下一个成功响应

HTTP/1.1 200 OK
Content-Type: application/json

{
"code":"ASYNC-SUCCESS"
"messsage":"成功"
"data":{
    "url":"/device/ECD0017R1/status/344fer55656563"
}
}

无论是成功或者失败,都应该包含code和message,code是成功或者错误的CODE定义,message是用户可阅读的的消息。

对于响应的JSON,还有如下设计建议

  • 需要明确的区分其属性是否为null,或者不存在
  • 返回的空集合,使用[],而不是null,或者不存在
  • 返回的浮点数或者长整形,如果客户端是JS,需要注意到有可能JS不支持,这情况下改成字符串

总有例外

本节包含了通常REST API 最佳实践,有其他资料对API 最佳实践有更严格的规定,架构师需要衡量是否采用这些严格规定, 本文列举了几个需要衡量的点

  • 充分使用HTTP Status Code,有些REST规范要求使用更多的HTTP Status Code表示,比如对于客户端错误,采用403表示权限不足,,404表示访问资源不存在使用。参考rfc9110 了解更多status含义。本书建议使用200,400,以及500即可,更详细的错误信息放在code里
  • REST API的GET查询不推荐携带Body。允许GET with Body,即允许GET查询携带Body。这是因为可能查询参数结构较为复杂,比如ES提供的/_search, 其body就是一个JSON格式。再比如,通常客户端JS框架会把页面查询条件的表单序列化成JSON。因此通常建议使用POST请求,且URL路径上增加查询动作,比如/books/search
  • 删除通常是用DELETE,如果是按照条件删除,可以使用POST,比如ES中的按照查询条件删除 POST /my-index-000001/_delete_by_query
  • REST API 大部分实践都要求参数是蛇行命名。本书建议,蛇行命名,以可以像Java那样驼峰命名

通常,基于GET请求的REST,意味着 通过URL分享此REST接口,其结果易于被客户端或者代理缓存,支持重定向等功能,如果GET支持BODY,那必须确认HTTP 代理或者是WEB 框架是否支持。

参考:

知行合一