出发点
本人最近负责部门930的一个核心需求,设计阶段主要负责数据库和api的设计。数据库的重要性无需多说,大家的设计理念也比较一致,评审很顺利。但api的评审意见却五花八门,着实让我惊了一番。
项目的api设计标准已经发布,是restful风格。但是项目的历史api中符合restful规范的寥寥可数。其次,评审过程中很多同事连restful风格都不了解,其对api设计的重要性看法可见一斑。
基于这种不重视api设计的普遍现象和本人极度重视api设计的观念冲突,认真梳理以下api的设计范式和应用场景是很有必要的。
正文
问题
如下是我们项目的一个api。
这个api看似是遵循restful设计的。但是其中有几个非常严重的问题:
- 前缀apps是一个无用的名词,软件中app几乎可以代表一切,这里只会增加api的复杂性
- asset/asset_view/asset_view不明所以,不知要表意什么。并且asset_view重复,同样只会增加api的复杂性
- on_list像是名词又像是动词,而且list在此处只是一个动作,不是一个资源,api语义不明。
- 获取资产信息采用post方法。
做了什么
此次需求设计注意到了上次的问题,需要避免。
常见的api设计规范有三:restful,rpc和graphql。在此不会详细描述三种规范,只通过例子表述其理念。
restful
大名鼎鼎的restful风格。其核心为资源,即如何从api操作中抽象出资源。并通过http方法(get,post,patch,put,delete等)表示对资源的操作。restful从03年提出,到10年之后风靡,我人为其和面向对象蓬勃发展有着很大的关系。如果把资源看作对象,资源的操作看作对象的方法,那么就是妥妥的面向对象在交互方面的实现。
github就是这方面的典范。如下面这个例子:
/repos/{owner}/{repo}/branches。通过路径参数来表示某人仓库资源下面的分支资源。通过查询参数来附加limit等属性。其好处是见名知义。例如我直接在路径后面加上某个commit即可以直接跳转到对应的commit,甚至不用翻页和和跳到对应的搜索框搜索。
更多信息请参考:GitHub REST API - GitHub Docs
rpc
rpc的api设计风格名声不如restful大,但是每个没有规范的开发者设计时多多少少会有此风格的影子。其在api路径中表示定义的动作,很符合我们用api表示一个操作的惯性思维。rpc的设计理念为命名空间/资源类型/具体操作。如下面为slack.com这个空间下api资源类型下对会话得到归档操作。
其特点是设计理念简单(但是随着需求扩展和功能增加,要设计好资源,动作并不简单),也很符合人们描述api需求的直觉。也是现在最常见的没有规范指导下api最贴近的风格。
更多信息请参考:conversations.archive method | Slack
graphql
graphql是facebook提出的api查询语言,在面对一个复杂的图状数据查询的时候很有效率。其理念为将图状结构的类型和对应key传送给后端,后端返回相同图状结构的值响应。
如下图即为官网的一个例子。定义好Query和User的类型和数据key,返回图状数据响应。
更多信息请参考:GraphQL 入门 | GraphQL
确认设计
从评审设计到确认设计经过两个步骤:
第一个过程是按照项目规范restful设计的api。以下为一个样例: 此需求为根据port和service查询对应的资产l。如查询port为22,service为ssh的资产。
url:inventory/ports/{port_id}/{services}/{service_id}/assets
method:GET
auth_required: true
timeout:30s
request
path params: port_id: type: int required: true example: 36 range: 1~65535 service_id: type: str required: true example: ssh query params: limit: type: int required: false default: 50 enum: - 10 - 20 - 50 - 100 - 200 - 500 offset: type: int required: false default: 0
response
{ "success": true | false, "total": 100, "data": [ { "asset_name": "xxx", "ip": "192.168.0.1", "owner": "xxx", } ] }
第二个过程是rpc的设计。从restful转为rpc设计,最主要的原因是因为restful设计需要明确资源,对于频繁变动的商业需求来说,开发工作量会很大。基于这方面考量,最终按照rpc的规范重新设计了api。
设计规范为统一使用POST请求方法,通过.action后缀来实现动作表述,目前只支持get,create,delete,upate,aggregate。具体api设计如下:
url:api/inventory/ports-services.assets.get
method:POST
auth_required: true
timeout:30s
reqeust
post body: port_id: type: int required: true example: 36 range: 1~65535 service_id: type: str required: true example: ssh limit: type: int required: false default: 50 enum: - 10 - 20 - 50 - 100 - 200 - 500 offset: type: int required: false default: 0
response
同restful设计的响应。
后续思考
restful的好看与不实用
在这次设计之前,本人一直人为restful就是神,无脑推崇restful反而被restful规范束缚。现在想来,restful对于部分需求(甚至可以说大多数商业需求)并不合适。
对比github的这种完美的github设计会发现restful场景限制。github是提供工具供我们使用的。资源相对好划分,需求变动也不大。如仓库,分支,提交的资源,不可能频繁变动,github定位为代码仓库,那么主要这次hi资源的CURD,和restful规范十分契合。但是回到我们的商业需求场景。用户需求千变万化。我们不可能一直提取资源,提取资源。甚至很多场景很难提取资源。
最后,restful本身只是一个风格,属于非常理想化的状态,算不上严谨的规范。比如一个资产的清点,报废,维修等等,都抽象称资源,并且需求频繁变动,设计起来是复杂费力的。
rpc的简单与复杂
rpc设计看似简单。但是对于对于一般场景来说,命名空间和资源类型十分固定。那么通过后缀来实现几乎api的所有语义,并不简单。例如github的 repos/{owner}/{repo}/branches分支apirpc的设计很可能会设计为repos.owner.repo.branches.get。这样和restful又有什么区别呢 。从rpc领域和行为的角度出发,个人人为设计为repos.branches.get比较好。
rpc的api设计可以说比restful的定义还要模糊。所以每个项目的api都要有自身一致的规范。比如资源的层级是多少,定义的行为有哪些等等。
初看graphql几乎可以覆盖所有的需求。但是grapql有很多明显的缺点。只能在某些特定情况下使用。
- 首先很多简单的需求通过graphql定义会过于复杂。获取登录验证码想必没有人愿意通过graphql来查询。
- 数据库必须以图状设计才能体现graphql的优势。要不然后端对于资源的逻辑处理会抓狂。
- 图状数据存储和图状api设计,查询子层节点或者重组结构会十分困难。