面试了N多的程序员,知道PUT的都寥寥无几,更别说HTTP状态码了,也对接过国内各大厂的API,竟没一家是遵守RESTful的!所以想要真正推行RESTful规范,那是真的困难重重,不光要培训和训练那些写API的程序员,还要跟合作方各种说服和引导。所以一直酝酿着想写个RESTful这个主题,但是由于自己在实践过程中,总是不断冒出新的问题,新的认识,所以一直不忍下手。刚过五一的这个大周末,闲在家没出门,写不了RESTful这个主题,整理一下RESTful资源这块的实践经验我觉得还是够格的。

RESTful接口成熟度模型

  1. Level 0:只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。
  2. Level 1:引入了资源的概念。每个资源有对应的标识符和表达。
  3. Level 2:使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。
  4. Level 3:使用 HATEOAS。

资源概述

如下是一个RESTful请求:

1
curl -X [HTTP Method] --data-urlencode [Request Data] --header [Request Header] --user-agent [User agent] [URL]
  1. 资源标识:[HTTP Method] [Schema]://[Host]:[Port]/[URI]
    • Schema:协议[http/https]
    • Host:服务器主机、IP或域名
    • Port:端口
    • URI:/[模块名称]/[模块版本]/[接口名]/[唯一标识]
    • HTTP Method:请求方法
  2. 资源操作
    • Request Header:请求头
    • Request Data:请求数据
    • Other:其他
  3. 资源表达
    • Response Header:响应头
    • Response Data:响应数据
    • Other:其他

资源标识

HTTP Method

1
2
3
4
5
6
7
|HTTP Method    |描述           |
|---            |---           |
|GET            |获取,查找     |
|POST           |新增创建       |
|PUT            |更新           |
|PATCH          |部分更新       |
|DELETE         |删除           |

资源URI

模块名称

1
一般为系统名称或者某一个微服务名称

模块版本

1
2
3
4
5
6
加入系统版本能使提升接口可用性(上下兼容)和降低重构代价。  
因为系统版本号放在uri的最前面,可以通过代理路到不同的接口实现,进而使新老版本共存直至平缓过渡后停掉老系统。  
一般如下几种情况需要变更版本号:
* 接口名称变更导致新老接口路由冲突  
例如:v1/orders?user_id=1 --> v2/:user_id/orders
* 接口入参或出参变更导致新老接口冲突

资源名称

假设模块地址为[module-uri]

资源型

1
2
3
4
5
6
    GET [module-uri]/orders            获取订单列表
    GET [module-uri]/orders/:id        根据id获取单个订单
    POST [module-uri]/orders           创建订单
    PUT [module-uri]/orders/:id        根据id更新订单
    PATCH [module-uri]/orders/:id      根据id部分更新订单
    DELETE [module-uri]/orders/:id     根据id删除订单

服务型

1
    GET [module-uri]/services/search    搜索服务

系统设置类

1
    PUT [module-uri]/settings/langueage          设置系统语言

复杂关联关系

当有非常复杂的管理关系,对关联关系这种实体的操作就会有多种理解,这是建议根据返回实体确定属于哪个资源,如果是关联关系,则定义为关系名称。
比如:staff->role->permission

  • GET/POST/PATCH/DELETE /staffs 获取、新增、修改、删除员工,员工属性可以有roles、permissions
  • GET/POST/PATCH/DELETE /roles 获取、新增、修改、删除角色,角色属性可以有permissions、staffs
  • GET/POST/PATCH/DELETE /perssions 获取、新增、修改、删除权限,权限属性可以有roles、staffs
  • GET/POST/PATCH/DELETE /staff_role_relations或者authorizations

对一个资源的多种操作

当标准动词已经不满足时,比如导入导出操作,有两种处理方式,一种是将定义新的的动词,还有一种是定义新的资源,由于定义动词需要该到框架,所以建议采用定义资源的方式。 假设数据资源为data

  • 新增数据:POST /datas
  • 修改数据:PATCH /datas/{id}
  • 删除数据:DELETE /datas/{id}
  • 查询数据:GET /datas/{id}
  • 生成数据:POST /data_generations
  • 校验数据:GET /data_validations
  • 导出数据:GET /data_export
  • 导入数据:POST /data_import

资源操作

请求头

  • Accept:服务器需要返回什么样的content。如果客户端要求返回”application/xml”,服务器端只能返回”application/json”,那么最好返回status code 406 not acceptable(RFC2616)。
  • If-Modified-Since/If-None-Match:如果客户端提供某个条件,那么当这条件满足时,才返回数据,否则返回304 not modified。
  • If-Match:在对某个资源做PUT/PATCH/DELETE操作时,服务器应该要求客户端提供If-Match头,只有客户端提供的Etag与服务器对应资源的Etag一致,才进行操作,否则返回412 precondition failed。

请求数据

资源表达

数据格式

采用hal+json规范。

数据类型

由于Number、Boolean和Null在不同编程语言会有不确定性,所以建议只使用String、Array、Object。

错误码

采用problem+json - RFC 7807规范。并将title作为错误码。参考API错误码规范

参考