最近使用 Backbone.js 开发客户端时发现了一个看似很小,实则非常麻烦的问题:
如何与非 CRUD 式的 RESTFul API 交互。
举个例子,有一个活动 activity
模型,它可以被开启或者停止,有两种解决方案:
- 使用 put 或者 patch,比如
/service/activities/:id
并传递参数{status: :active}
- 使用额外的 endpoint, 比如
/service/activities/:id/active
如果使用第一种方式,那么就是标准的 CRUD 式的 API,Backbone.js
默认就支持。
但是实际上开启一个活动是一个比较大的动作,可能需要调用其他模块的功能,不是简单的改一个字段。
因此从 api 设计的角度我是倾向于使用第二种方式的。可是奇怪的是关于 Backbone.js 调用 CRUD 以外 的 API 的文章非常少,我翻遍 google 就勉强找到了这篇。通过临时改变 model 的 url 属性来实现的。 并没有太让我满意。
正好晚上向 vincent 请教了一下这个问题,因为讨论的重点是 api 设计而不是 backbone 的具体实现, 反而开阔了我的思路。
重新复习了一下 RESTful 的相关知识,发现了一个常见误区:
最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。
对于这个观点争论不少,毕竟类似于 /service/activities/:id/active
这样的设计还是很常见的。 这个 AOE 范围有点大。。。stackoverflow上有个讨论可以参考。其中我比较赞同的一点是:
In general, when you think you need more verbs, it may actually mean
that your resources need to be re-identified.
这里 提出的三种解决方案也可以参考。
举个例子:
你是想打印一本书,还是发起一个打印的订单请求?
一样的道理,我是要激活一个活动,还是发起一个激活活动的请求?
这个例子中,使用了名词代替动词。一旦变成了名词,我立马想到可以新建一个 model。
这下,只要创建一个 Backbone.js 的模型 startActivity
, 把它的 url 指向
/service/activities/:id/active
。
当发起请求后(激活由于业务逻辑的限制,未必就会成功),后续的逻辑就可以都放在这个 startActivity
对象中。
如果激活失败就显示错误信息,成功则通知 activity
模型重新渲染自己的视图。
这样结构更加清晰。这未必是最好的解决方案,但是不失为一种不错的思路。
这次我犯的错误是,一开始总是从 Backbone.js 的方面找问题。想要把 Backbone.js 变成 一把锤子来解决这个钉子。二是对 RESTful 这样比较基础的知识的理解还是不够,否则思路也不会这么局限。