ikki@github.io:~$

Restful设计

Restful设计

实践中的困惑

  • 接口名称:通常会设计一个接口叫 /add 或者是 /insert, 后台对应数据的插入操作,但是从来没有互相约束为 一定叫add 还是叫insert
  • 不幂等的设计:有时候会对某个开关设计一个叫 /toggle 或者 /switch, 调用一次会将这个状态置为相反的状态,在网络出现问题时,会出现和预期不一致的状态
  • 杂乱的状态码设计:我们永远无法预料到程序里所有可能出现的错误,但是我们总是能知道我们期望的正确结果

Restful 的设计哲学

Restful1 是由 Roy Thomas Fielding2 在2000 年的博士论文中提出的。

  • 一切皆资源 这令人想到 Linux 的设计哲学:一切皆文件

  • 每个资源由唯一URL标识

  • 资源处理使用 POST, GET, PUT, DELETE方法对应 创建,读取, 更新,删除操作,此处对应数据库的CURD操作

  • 幂等性 一个请求执行一次和执行N次的结果是一致的

  • 状态码 使用Http Status

处理一些蛋疼的例子

  • 前提:restful 是一种设计风格,并不是明确的规范

一切皆资源

  • 系统通常有一个用户登录,我们设计为 /login, 但是设计为 POST /sessions 就略显别扭,虽然这个在逻辑上是说的通的
    • POST /session 笔者见过这样的实践
  • 播放音乐的接口,理解为创建一个播放状态,可以变更为 POST /musics/{id}?status=play , 修改某个音乐的状态为播放

方法动词

  • 当url 严格设计名词时, 使用 对应的请求方法,则表述性会更强
方法 操作 描述
GET 获取 查询,比如 GET /cars/231, 获取编号为231的车子信息
POST 新建 POST 请求是不幂等的,因为我们允许有多辆属性相同的车子
PUT 更新 修改, 比如 PUT /cars/231, 修改标号为231 的车辆信息
DELETE 删除 比如 DELETE /cars/231, 删除编号231 的车辆信息
GET 和 POST 区别
  • GET只接受ASCII 字符且长度受限, POST 不受限
    • GET 设计时考虑参数可读性,参数的数量
  • GET 请求更加容易被浏览器刷新,要考虑幂等性
  • GET 请求容易被缓存
实战建议
  • 路径参数 (spring mvc 中 使用 @PathVariable 注解的参数)
    • 建议处理可枚举的参数, 因为对监控不太友好, 按路径可能会统计出很多指标;
    • 但是比如在一个多租户的系统中, 用来传递租户id,恰好有自然被 监控系统发现并自动分组
    • 关于路径参数中使用id 以符合 restful 风格的问题:在没有副作用的情况下使用,(可能作者当年也没有想到,互联网的数据是一个指数级的增长吧,作者当初也许是设想在一个 MIS 系统中,很自然的传递和查看一些数据吧)。
  • queryString (spring mvc 中 使用 @RequestParameter 注解的参数)
    • 参考意见是: 如果一个地址很方便的被 使用者 copy/paste 进行传递,那么那建议 使用 GET + QueryString 的组合。比如,在监控系统中, 把查询条件在 queryString 中 传递,方便使用中,传递信息,打开浏览器 接收者便能直接查看到问题所在,而不是按照 对方所说的设置查询参数。 比如 kibana 和 grafana 的 url 通常都是这样很长的。
    • 要注意GET URI 字符受限问题,比如某个资源的翻页查询,通常只有有限的5~6 个参数,不妨使用 GET + QueryString 组合。
    • 如果查询条件过于复杂,服务器不能提供响应,不妨改为POST + 202 的异步组合, facebook 的 广告数据接口有类似的设计

使用正确的状态码

  • 比如404, 表示接口找不到呢还是资源接口不存在?
    • 如果是被解读为资源不存在,那就是一个业务问题,对用户来说是能理解的,是合情合理的
    • 如果是被解读接口不存在,这就是一个技术问题,是应该消灭在开发阶段的, 而不应该暴露在生产环境的。
    • 综上, restful 原本就是 表示资源的,属于业务范畴,用404 表示资源不存在也是合理的。如果404 被解读成了接口找不到,那就是开发的问题。按照时间线来说,现有接口,后提供资源服务。
  • 状态码会不会不够用?绝大部分情况下,一个REST API 总是返回一个甚至2个期望的结果,但是出现异常的情况却有很多,所以与其期望明确定义好每一种异常情况,不如做好异常业务状态的提示信息的用户体验

    状态码 说明
    1xx 消息
    2xx 成功
    3xx 重定向
    4xx 错误请求
    5xx 服务器错误

Richardson Maturity Model

级别 说明
Level 0 The swarmp of POX
Level 1 Resources
Level 2 HTTP Verbs
Level 3 Hypermedia Controls

这个不展开细说,可能我们大部分的API 处于 1~2 或 2~3 的一个中间状态, 尝试了就是最好的。

我想说的是,不管restful 风格在实践过程中有多少 争议,既然存在这么一个 非标准的准则, 作为开发就应该去布道,思考,并反复实践,抽象我们的业务,使我们的API 尽量去符合和遵循这样一个东西。

总结

  • 如果你不熟悉Restful, 还是多在各种业务场景下练习抽象思维
  • 如果某个业务场景不适合使用Restful, 可以试试变通的抽象
  • 跟详细的定义http 定义可以 阅读 rfc7231, 其中详细描述了各请求状态码的含义

  1. (Resource) Representational State Transfer, 资源表征状态转移 

  2. Roy Thomas Fielding 是HTTP 协议的主要设计者、Apache服务器的作者之一、Apache基金会的第一任主席。