基于REST的接口定义思考
因前后台分离,使用AngularJS作为纯前端,需要设计一组基于JSON的API,统一前后台的接口规范。
该组API需要有如下特性:
- 统一接口调用风格
- 统一接口返回码
- 以尽可能简单的方式提供调用说明
- 接口支持增删改查等基本功能,也有其他自定义功能,如 软删除(IsDeleted = 1)
调查
首先想到的是REST,设计一套RESTful的方案,如 https://www.dummy.com/api/menu 提供最MENU的管理 使用HTTP动词作为操作管理
GET : 获取菜单 POST : 创建菜单 PUT : 创建或更新菜单 DELETE : 删除菜单
现实使用的时候发现如下问题:
- GET 接口可以返回一个数据{menu} 或者一组数据[{menu},{menu},{menu}], 后台要单独处理
1 2 3 4
如 GET /menu 返回 一组[{menu},{menu},{menu}] GET /menu/1 通过路由映射到后台 Menu(int id) 方法,返回一个menu 但是 如果我的查询条件中就有包含 {id:1} 这样的条件,那么会出现 GET /menu , post data {id:1}, 这样会自动路由到 Menu(int id) 方法,期望的是返回menu数组,其中包含一个menu [{menu}],后台只返回了一个{menu}
按照wikipedia的举例,menu和menus是分开的
1
2
GET https://www.store.com/products
GET https://www.store.com/product/12345
-
REST推荐通过状态码返回出错信息
对于HTTP的状态吗,常规的我只记得
- 200 返回正常
- 401 认证错误
- 404 找不到页面
- 500 服务器错误
其他的只能现用现查,这样当后台返回409,我查完得知表示资源冲突,但是具体冲突是因为什么,还是需要和后台沟通好。
另一个问题是,直观的感觉,返回200就代表正常,如果4xx,5xx就感觉是服务器错误,和我的应用程序无关才是,3xx基本没碰到过。
但是这里409是我们自己后台抛出的,和服务器无关。我不希望将系统错误,传输错误和应用程序错误混为一谈。
- 只有四个动词,不够描述后台所有的操作,按照REST的推荐,应该都封装到PUT里
而微信公众号的接口设计却没有完全使用这种方式
1
2
3
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
百度地图的API也是
1
https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-placeapi
- 虽然HTTP的动作只有4个,但是后台为了操作明了,同样还是需要创建多个function来表示不同的操作内容,前台AngularJS同样需要创建多个function来表示动作
- 某些浏览器不支持PUT,DELETE等从此(这个问题现在好像不成问题了)
安全
按照微信API的方式,统一使用access_token,就是说每次请求都需要带入access_token,防止跨站攻击
思考
我一直想强行用REST的方法整理接口,因为这个标准存在了,与别人沟通的时候,公认约定总归好于个人约定
但是使用过程中诸多不顺,因此查找文章以及知名开放API的提供者,比如微信公众号API
终于找到了支撑自己的几个理由:
- REST是设计风格而不是标准,这也是很多地方说RESTful的原因,既然是风格那也就是可以有选择的使用
- 微信的API设计,并不是完全的REST,他的动词都是放在资源的后面,这点结合后台api的function非常方便,微信API的使用频率和使用量应该可以打消我们其他的顾虑,比如效率,安全
- SOAP也是一种WEB访问协议,他封装了操作,用XMLSpy访问SOAP的wsdl,可以得到可以操作的方法,只是使用方法复杂,我们为何不可把操作结合到REST风格的url里,这样既可知道调用方法,又不需通过复杂的方式取得
结论
找到了理由,自然要设计适合自己的API,而不是用REST就全部REST
结论很简单,即使用REST那样清晰明了的URL风格,结合SOAP的动作的描述方式,对动作在URL进行描述。 所有的返回都是200,应用程序错误用errcode表示
接口只使用GET 和POST 两种动词 , 这样不存在浏览器兼容问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
https://www.dummy.com/api/menu/get 表示获取菜单(单个记录)
https://www.dummy.com/api/menu/query 表示查询菜单(多条记录),post数据中加入查询条件
https://www.dummy.com/api/menu/create 表示创建菜单
https://www.dummy.com/api/menu/update 表示更新菜单
https://www.dummy.com/api/menu/delete 表示删除菜单
结合 AngularJS的 Resource
app.controller('TableCtrl',function($scope, $routeParams, $resource) {
var User = $resource('/api/user/:_action',
{id:"@id"},
{
get: {method:"GET", params:{_action:"get"}},
query: {method:"POST", params:{_action:"query"}, isArray:true},
create:{method:"POST", params:{_action:"create"}},
update:{method:"POST", params:{_action:"update"}},
delete:{method:"GET", params:{_action:"delete"}},
});
$scope.users = User.query({ticket:"1155"});
$scope.id = $routeParams.id;
if($scope.id){
$scope.user = User.get({id:$scope.id})
}
});
- 不需要参数的,或者参数在URL中提供的使用GET方法
1 2 3
https://www.dummy.com/api/menu/get https://www.dummy.com/api/menu/get?id=5 https://www.dummy.com/api/menu/delete?id=6
- 需要提交数据的使用POST方法
1
2
3
4
5
6
7
8
9
10
https://www.dummy.com/api/menu/create
POST数据如下
{
name:"测试菜单"
}
https://www.dummy.com/api/menu/update
{
id:"5",
name:"新菜单"
}
- 返回格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
操作正确
{
"errcode":0,
"errmsg":"ok"
}
操作错误
{
"errcode": 401,
"errmsg":"No Access"
}
操作正确有数据
{
"errcode":0,
"errmsg":"ok",
"menu":{
"name":"测试菜单"
}
}
https://www.dummy.com/api/menu/help 获取menu下的接口说明文档,以json返回,这样接口就可以像SOAP那样,自说明 这个只是一个设想,需完善
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"get":{
"http":"get",
"request":{},
"response":{
"errcode":0,
"errmsg":"ok"
}
},
"create":{
"request":{
},
"response":{
}
}
}
参考文章
https://zh.wikipedia.org/zh-cn/REST https://zh.wikipedia.org/zh-cn/SOAP https://www.infoq.com/cn/articles/rest-introduction https://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html https://mindhacks.cn/2009/01/16/hammers-and-nails/ https://blog.csdn.net/pongba/article/details/3796771
多年以后的后记
后来又了解了很多这方面的内容,才知道了 Restful 和 Json RPC 之争,我上述的设计方法就试属于 Json RPC, 但是也是有缺点的,比如动词设置不规范,不遵守HTTP原生规则,导致调用方多出很多沟通成本,微信公众号的API也是被吐槽已久。
关于设计 Restful 的API也是可以的,只是需要花更多的时间理清实体,比如 github 的 api, 其中做 migration https://docs.github.com/en/rest/reference/migrations 就是如下风格
1
POST /orgs/{org}/migrations
下载一个 mrgration
1
GET /orgs/{org}/migrations/{migration_id}/archive
解锁一个 migration 的 repo
1
DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock
并没有用到 migrate, lock 等动词
关于优秀的API设计,可以参考如下 微软 和 Paypal 的 API Guideline
https://github.com/microsoft/api-guidelines
https://github.com/paypal/api-standards/blob/master/api-style-guide.md
【全文完】