• 我们的API必须遵循RESTful风格吗?
  • 发布于 2个月前
  • 171 热度
    0 评论
从事Web开发的童鞋对RESTful API很多人都不陌生,不同项目的接口形式千奇百怪,有些声称使用RESTful,但是实际上并不完全符合规范,不禁让我思考,到底要不要使用RESTful API的风格?

举个例子,相信大家见过不少下面风格的API:
用途 请求方法 命名
获取用户列表 GET /getUserList
获取单个用户信息 GET /getUserInfo?id=1
新增用户 POST /createUser
更新用户 POST /updateUser
删除用户 POST /deleteUser
这是一个最简单的CURD接口,说实话,我并不反对这种形式的API,虽然说看上去俗了点,但是至少见名知意,除了获取数据使用GET,其它一律POST,简单粗暴,没什么大问题。如果展开说,上面的这种形式在复杂的情况下,无法更好的表达接口的含义,比如说假设你要写一个获取用户的好友列表接口,那可能就得这么写:
获取用户好友列表    GET   /getUserFriends?user_id=1
这种命名方式在复杂情况下会导致接口名字越来越长。。。可读性就变的差了,而且一眼看不出来数据之间的关系,不好分类,假设有些人名字写的差就更难解读了。

如果使用RESTful则可以采用这种形式:
获取用户好友列表    GET   /users/:id/friends
传统的接口采用动词+接口名称的形式来表达含义,而RESTful推荐使用HTTP请求方式来定义,比如GET用于获取数据、POST用于创建、PUT用于修改、DELETE用于删除。上面这些接口如果使用RESTful形式改写就是这样:
用途 请求方法 命名
获取用户列表 GET /user
获取单个用户信息 GET /user/:id
新增用户 POST /user
更新用户 PUT /user/:id
删除用户 DELETE /user/:id
关于是用user还是users这块也有一个分歧,有些人觉得应该是用复数,因为有多个用户,所以获取用户得用getUsers,这个问题同样存在数据表名称上面。我觉得接口用不用复数取决于数据表名称用不用,如果数据表就是users,那接口最好也用,保持一致。至于说数据表名称该不该用复数,很多人都有不同的看法,在项目开始的时候统一明确的一个规范就行了,如果你要问我,我建议使用单数。

除此之外,RESTful还推荐通过HTTP状态码来区分结果,比如200表示正常,403表示没有权限,传统的接口往往统一使用200,然后在响应内容里面去区分。简单说,RESTful的设计初衷是把这些数据实体当成一种资源,通过URL去定位资源,用HTTP描述操作,达到以下几个目标:
1.看URL就知道要什么
2.看HTTP方法就知道干什么
3.看HTTP状态码就知道结果如何
在我的职业生涯里面见过很多看似的RESTful的风格,但是实际上也不完全是,比如说PUT、DELETE的时候id是以http query string的方式传递,而不是path:
# 堆代码 duidaima.com
删除用户    DELETE    /user?id=1
还有接口是直接把所有参数放到body里面传递,虽然说这些方式都可以,也不麻烦,因为现在很多Web框架都这些参数的处理都很方便,但是感觉还是不伦不类,当然有时候也是逼不得已,比如有些表的设计没有单一id主键,只有一个复合主键,这就很难使用path这种形式。

并非完美
RESTful的设计规范很理想,但是并不适合所有的项目,因为很多接口不是纯粹的CURD操作,它不仅仅是对一张表的增删改查,这导致很难通过URL去表达含义。对于单纯的获取数据、修改数据的接口RESTful比较合适,但是当你的接口里面包含非常复杂的业务逻辑的时候,RESTful就非常麻烦了,根本无处下手。

举个例子,一个根据用户的住址获取附近的超市的接口,这样的接口如果使用传统的方式可能是这样:
/getUserNearbySupermarket?user_id=1
如果采用RESTful就存在一个问题,这到底是围绕着用户还是超市来设计呢?
/user/:id/nearby_supermarket
/supermarket/nearby?user_id=1
/user_nearby_supermarket?user_id=1
以上这3个例子你们觉得哪个好?对此我也纠结了很久,最后在参考了很多云厂商的API之后,个人觉得第一种比较合适。有些时候RESTful API定义的GET、POST、PUT、DELETE4个常用方式并不能满足所有的应用场景,或者不够清晰,比如说业务有个场景是要清空用户黑名单,这属于PUT还是DELETE?

从用户的角度来说说PUT,因为我是修改了用户的附属属性,但是从黑名单的角度是删除了数据,都有一定的道理。或许,我们可以这么设计
POST    /user/:id/blacklist/empty
我们可以认为blacklist是用户的属性,empty是一种动作,是不是更加清晰一点,至于方法是用POST还是PUT,区别都不大。

最佳实践
对于开放平台的接口,比如说一个云厂商,需要提供对应的sdk或者api给用户使用,推荐使用RESTful,实际上国外很多大厂的api是非常标准的RESTful风格,国内某大厂除外。云服务本身就是提供资源,无论是服务器、IP、域名,它都是一种网络资源,而RESTful天生就是为了描述资源,并且云服务的操作逻辑简单,没有复杂的业务逻辑,对于服务器无法就是创建、查看、修改、删除、重启等。

以前用过vultr的API,就是非常规范的RESTful风格,看着很舒服:
用途 请求方法 命名
获取实例 GET /instances
获取单个实例 GET /instances/:id
新增实例 POST /instances
更新实例 PUT /instances/:id
删除实例 DELETE /instances/:id
启动实例 POST /instances/:id/start
批量启动实例 POST /instances/start
值得一说的是启动这个操作,它有2种形式,一个是操作单个实例,参数放在path里面,还有一个是批量操作,是有多个id,这时候参数是放在json body里面。但是对于很多公司内部系统的接口,RESTful往往并不是最佳的选择,如果为了简单方便,不考虑这些乱七八糟的规范,我建议除了获取数据使用GET,其它一律使用POST。
用途 请求方法 命名
获取用户列表 GET /user
新增用户 POST /user
更新用户 POST /user
删除用户 POST /user
比如上面这个写法,第一个GET接口要支持多个参数过滤,比如说可以这么写/user?id=1,相当于获取单个用户,除了GET之外的接口参数全部放到body里面,而且统一采用JSON形式。

还有一个响应状态码的问题,个人觉得还是统一200比较合适,在响应体内部另外定义code来表达结果,也就是说不依赖HTTP状态码,虽然有点不符合RESTful的规范,但是兼容性好,因为你不知道哪个中间件会怎么处理。

最后总结一下:
对于要不要使用RESTful风格的API这个问题没有明确的答案,如果你的应用逻辑简单,大多数是对一些数据表的增删改查,可以考虑使用RESTful风格的接口,相反如果你的应用业务逻辑复杂,也可以不用。如果考虑到这是一个对外公开的OpenAPI,建议使用RESTful风格,毕竟这也算是一种业界共识,你整个什么getXXXByXXX显的确实有点low啊。

其实我们也可以2者混用,根据实际情况来决定,毕竟这只是一个接口路由,没必要纠结太多,大部分Web框架都支持各种形式的路由参数,对于简单的CURD可以使用RESTful,对于复杂的业务逻辑则特殊处理。
用户评论