近日百家饭OpenAPI平台正在进行v0.7.0版本的研发,这个版本我们期望引入API的透视图,既然要透视API的功能设计,那首先要讲清楚API的设计规则,今天就想和大家讨论一下,什么样的API设计规则是合适的。
我们拿自己原来早期做的一个项目来做个反例,这是这个项目的API透视图
大家觉得有什么问题?是不是有这么几个:
- 一个controller好像对应了很多数据类型
- controller的名称和数据的名称没有关系
- 还有种数据叫shuchu?这是什么鬼
细的来说,第一点和第二点是controller和model的名称不一致,app-controller(/app/xxx)用于DataAuth、Template等多种数据的输入,但是app这个名称并没有资源含义,而且app-controller和user-controller操作了太多的数据类型。
那当时为什么这么设计呢,其实只是因为这个项目是一个小程序的后台,所以后台开发的时候就直接使用了AppController这个名字,而且把所有的操作都放到了这个controller里面。
那我们后来遇到了什么问题呢,首先就是维护复杂,AppController越来越大,任何一个错误的修改都要定位很久,其次是协作和沟通变得异常艰难,无论是要调接口的前台还是后期参与的其他开发,都很难理解代码的业务逻辑。
第三点在这个图的右面第四个数据,他的名称命名成了shuchu,当时命名的唯一原因是他是一个接口的输出数据类型,这也是为什么我们要说,一定是资源决定api,而不是api决定资源的原因。我们首先需要确定的是api操作了哪几类资源,把资源按他的属性命令,再去决定api的名称,不能因为api导入了数据,就把数据简单命名为ImportedData,或者像我们一样,因为输出了一种数据,就命名成了Shuchu。
所以这里也提醒所有的开发小伙伴,特别是像我们一样,小小厂的小伙伴
后台开发,先做数据资源设计,搞清楚所有资源后,再去堆代码,做API设计,不着急!
后台设计的现代原则:RESTful
有了这么个反例之后,那小伙伴肯定要问,那具体这个api设计有没有什么指导原则,毫无疑问的可以说,API在当今,最核心的设计理念就是RESTful,这是个2000年就提出来的概念,这个概念其实是一个博士论文提出来的,维基百科说他是Roy Thomas Fielding博士于2000年在他的博士论文[1]中提出来的一种万维网软件架构风格。百度百科说他“首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一”。
实际上,目前所有的API设计逻辑,基本都是REST为基础结构的,而RESTful的基础要义,我们认为就是一个很简单的原则:
URL及后台分层设计因以数据为导向,按数据进行分类,进行url、controller、service、mapper等多层设计
RESTful的API结构
为什么这么说,看看维基百科的介绍(注意里面我标红的字眼)
需要注意的是,REST是设计风格而不是标准。REST通常基于HTTP、URI、XML以及HTML这些现有的广泛流行的协议和标准。
- 资源是由URI来指定。
- 对资源的操作包括获取、创建、修改和删除,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
- 通过操作资源的表现形式来操作资源。
- 资源的表现形式则是XML或者HTML,取决于读者是机器还是人、是消费Web服务的客户软件还是Web浏览器。当然也可以是任何其他的格式,例如JSON。
假设我开发了一个图书管应用,这个应用需要处理的对象有两种,一个是书(book),一个是书架(shelf),一个是借书人(user),那我最早接触Restful的ROR举例,rails命令可以创建3个controller,并自动的创建相关的函数,book的话,就会创建以下接口:
#获得所有图书
get '/books' => "books#index", as => "books"
#获得单个图书详情
get 'book/:id' => "books#show", as => "book"
#打开创建图书页面
get '/books/new' => "books#new", as => "new_book"
#提交创建图书
post '/books' => "books#create", as => "books"
#打开编辑图书页面
get '/books/:id/edit' => "books#edit", as => "edit_book"
#更新一个具体的图书
put '/book/:id' => "book#update", as => "book"
#删除一个图书
delete '/books/:id' => "books#destroy", as => "book"
其中,还有些单复数的接口区别,以便表征是操作多个还是单个,我记得ROR连一些特殊单词的复数也是正确的,person/people,man/men,有兴趣可以看这个:一篇文章讲这个单复数规则),这样的设计使得我们对于url的理解就很简单了:
- 一套统一的url前缀表示一类资源:(上面都是/book...)
- get一定是获取,post表示创建,put表示更新,delete表示删除(最新的nodejs express框架,我看到还引入了一个新的Patch操作用于表示更新局部)。
- 处理单个的,url中一定带id
那对应的controller就会对应7个函数,这七个函数的规范命令如下(后来还增加了UpdateMany和DeleteMany)
ReadMany
Read
Update
Delete
New
Create
Edit
示意图如下:
RESTful的灵活处理原则
不需要一定是这7个函数
当然不是所有的API都可以完美的处理成RESTful的资源,我们经常遇到一个问题,登录到底要怎么设计,登录属于对User的Update么?还是属于Create?要是我要分别获得用户的头像、属性、级别、余额,我是在read接口里用if-else分别处理这不同的数据返回么?
我们建议是
整体符合RESTful结构的url框架下,灵活处理复杂类型
那上面这个例子来说,user我们就不处理成这其中函数了,而处理成
Login (对应/user/login)
Register (对应user/register)
Head (对应user/head)
Info (对应user/info)
……
这样的controller结构,url的第一级仍然清楚的表明了是有关user的controller,同时,操作的入口也更为清楚。
不一定非要用put/patch
我们在应用RESTful的时候还遇到一个很大的问题,很多防火墙根本就不允许除了POST/GET之外的其他操作手段,如果非要用RESTful,RoR包括很多其他的框架支持通过query参数来指定方法,比如POST /book?METHOD=PUT会等效于PUT /book,这样的设计应用当然没问题,但是从接口的可阅读性上就查了好多,尤其在给甲方提交文档的时候,很多人会很不解,这种复杂的模式的意义是什么。我个人觉得也确实像脱了裤子FP。这种情况下就可以参考第一种例外模式来设计。
总结下来,那设计API的原则我们建议如下:
- 数据设计优于API设计,API设计为数据设计服务
- 除部分公共数据之外,数据和API分组(Controller)最好一一对应,一类API服务一类数据
- 使用一些统一规则来处理统一操作,例如ReadMany就是获取全部,最好沿用RESTful的七函数及其url规则,复杂数据类型再使用特殊模式
那我们如果真要设计一个能够清楚表明API设计优劣的透视图的话,我觉得这个图至少要有这几个功能:
- 数据视图优先,可以展开数据和API的关系
- 可以清楚的表示API Controller和数据的关系,是否有复杂的1对N或交叉问题
- 可以展开url的调用细节,以便查询RESTful的设计是否合理
这好难啊,好像现在的beta版本还不行,我们尽力吧……