善用 RESTful 风格

分歧

昨天开会讨论概要设计的时候,说到 API 的设计,大家分歧比较大。

具体举个例子:实现一个审核管理系统的功能。用户既可以是提交者角色也可能是审核者角色。提交者既可以提交审核,也可以撤销已提交的。审核者对于交给自己审核的任务,既可以批准,也可以驳回。用户可以看到两个列表,“我提交的review”和“需我来审核的review”。

分歧1——获取资源

依照我的设计,获取的相关操作分别是:

GET /my_review/list (我提交的 review)
GET /review/list (需我来审核的 review)
(以上 API,有其他参数,分页、按type筛选、搜索等等)

GET /my_review/search (在我提交的 review 中搜索)
GET /review/search (在需我审核的 review 中搜索)
(以上 API,有一个  参数表示搜索的关键词)

而其他一些人的观点是,这些接口不符合 RESTful 的规范,事实上这些接口都应该归为一个接口——

GET /reviews
(在GET 请求的参数中,完成对“我的 review”、“我审核的 review”,分页、筛选、搜索的功能)

因为 RESTful 说:url 是统一资源定位符,无论是“我提交的”还是“我审核的”,它们都是 review 的一种,只是筛选出的类型不同而已,而 search 也属于一种对资源的筛选。所以 url 都应该用/reviews,表示对 reviews 这个资源的获取,如果这资源有子资源,那么可以继续向后加reviews/<int:id>

分歧2——对资源的操作

我设计的提交、撤销、批准、打回这些操作API是这样的:

POST /my_review/submit
POST /my_review/cancel
POST /review/pass
POST /review/fail

而支持RESTful 又说:对资源的操作,增删改查,应当使用 HTTP 的 method 来区分,即:

GET 获取表示
POST 创建子资源/部分更新资源
PUT 通过替换的方式更新资源
DELETE 删除资源

理解RESTful (参考链接)

其实这个分歧不应该过度讨论的,但是本着认真的精神和统一化的愿景,API的设计还是需要仔细斟酌考虑的。下面我就阐述一下我认为的 RESTful 风格的优劣,以及我对 API 风格的设计。

RESTful 风格的优劣

首先说明一个事实,RESTful 不是一个规范标准,而是一种风格。并非指定 API 就一定要遵循 RESTful。

以下是一些我对 RESTful 的看法。

优点

  1. 把一切接口都定义为对资源的操作,整齐规范,从资源的角度思考问题,使得问题化繁为简。
  2. 充分利用RFC的标准,利用 HTTP 的 method,对资源的操作更清晰,操作的职责泾渭分明。

缺点

  1. 并非所有问题都容易从资源的角度思考,例如:用户登录、登出,(tips:登陆登出可以看做对 session(会话)这种资源的操作……)
  2. API 的使用者API 的开发者更难理解 API 的含义,因为 API 的使用者对资源的了解没有 API 的开发者多且深入。例如:
    有个支付的接口,在后端看来,支付成功、失败都是对这个订单状态的变更。所以 API 应当是:
    POST /orders/12,在POST的 body里有关于支付相关的参数。

    但站在前端、客户端的角度来看,他们不知道你的数据层有哪些资源,也不知道一次支付动作完成了哪些复杂的数据变化,他们只关心用户此刻做了一个操作——支付,但在 API 中没有 pay 这样一个动词,而是 order这样一个名词,一定会觉得困惑。

  3. 操作动作很多的时候,对于复杂的操作不易用 method 涵盖,例如:撤销、打回、通过、转交,都能可以归为对 review 资源状态的更新。

RESTful 的适用场景

从请求类型的说起

为什么如今的 web API 普遍使用 GET、POST而很少用 PUT、DELETE、PATCH、COPY、MOVE等等这些method呢?

因为早年很多server容器、客户端协议对除了 GET、POST 以外的支持兼容还不够完善。但是如今使用 PUT、DELETE都不是什么问题了。那么是不是就要大力发扬 HTTP 的这些 method 了呢?

当然不是,首先,这些 method 的诞生包括它们关于“等幂”、“不等幂”这些性质的规定,都是从资源的角度考虑的产物。

早先年称之为 web1.0的时代,可不就是将一个一个资源体连接起来,获取数据,上传数据,都是很普遍的操作,很多 url 之中甚至会有这样的形式:

http://xx.com/resource/~cyh/home/wiki/aa.html

时至今日,大多数的web服务也可以归结为对资源(信息、数据)的一种收集再分发

但越来越多的web服务,趋向于更多样的交互、更多变的数据流向、更复杂的权限控制,如果单从资源的角度思考问题,对于这些应用而言已经不适合。

举几个例子

新浪微博 API

获取:
Alt text

操作:
Alt text

以上的 API 乍一看是 RESTful 的,但仔细看过会发现,url里有 show、query、repost 等动词;通过 id获取的 API,id也没有体现在 url 中;而且对于“微博”这样一个资源,statuses/xxx后面的 xxx 这部分也不是它的子资源,而是筛选条件

其实也可以把这种风格看做是 RESTful 风格优化后的,好处是显而易见的:

  1. 接口功能通过阅读 URL 可以直接明白。
  2. 通过对接口的定义,直接传达我们提供了哪些服务,不需要使用者通过传参的不同,来区别不同的服务

腾讯和微信 API

腾讯应用:

Alt text

RESTful 规定 API 版本号不能在 url 中,应该在 HTTP Header 中,但是在 url 中的话,使用者更能清楚的知道自己正在用的是哪个版本,从产品的角度而言是比较合适的。

微信公众号:

Alt text

总体而言腾讯的接口看起来设计也比较很烂,像是历史遗留问题,但是你也能体会到这样的接口的优势在哪里。

Google Map

总拿国内没情怀的 IT企业说没意思,来看看谷歌的 API:

Alt text

其实总体看下来,大家好像没有谁把 RESTful 当回事儿。

偏资源型与偏服务型

其实使用 RESTful 风格最标准、最完善的也有,例如:七牛云、又拍云。

因为它们是做CDN、云存储服务的,使用 RESTful 最合适不过了,PUT 传文件、GET 下载文件、DELETE 删除文件等等。

而我们纵观其他许多“偏服务”的企业,它们设计的 API 就基本不会符合 RESTful。

偏服务型的 API 应该是什么样的风格?

那么我就根据我以前用过的各种 API 和写过的 API,在此斗胆总结一种适合偏服务型应用的 API 风格吧!

  1. 只使用 GET 和 POST,GET 的职责是“获取”,POST 的职责是“动作”
  2. URL 的命名方式:
    服务名称+子服务名称…+对象(id、obj等,可选)+动作(可选)

    *“可选”的意思是,设计 API 的时候视语义而定,选一种方式。而不是说设计出两者都兼容的 API。

    /<server>/<sub_server>.../[<object>]/[<action>]


    GET:
    /user/detail/21 (获取 id 为21的用户详情)
    /user/detail/21/get(获取 id 为21的用户详情)
    /user/friend/list (获取当前用户的所有好友)
    /user/friend (获取当前用户所有的好友)

    *user是一个总服务模块,detail、friend属于它的子服务


    POST:
    /user/friend/add (添加好友)
    /user/friend/21/edit_comment(编辑好友的备注)
    /massage/text/send (发送文本消息)
    /massage/34/repost (转发id 为34的消息)

    *message是一个总服务模块,text、img、voice 都属于它的子服务

  3. 版本号、response的文本格式、测试线上环境等信息。尽量在 URL 的最前部给出,方便开发。例如:
    /test/3.2/json/……
    /api/2.1/xml/……
    api.xxx.com/2.1/xml/……
    apitest.xxx.com/2.1/xml/……

(暂时就想到这些)

这种风格更适合于设计服务模块-子服务-服务的对象-服务动作这种以服务为主的应API。

总之,API 设计没有什么规范,每种风格也不是万能的,选对它的适用场景就好。