写个接口,获取动物详情,URL 风格有如下三种:
# Restful API 标准风格:
/zoos/:id/animals/:id
# 风格2
/zoos/animals/:id
# 风格3
/animal/:id
我个人认为 restful 标准的 URL 信息太冗余了,通过 animal_id 就可以直接获取动物详情,为什么还需要一个 zoo_id?即便给了 zoo_id,我相信后台在查询的时候也不会使用这个字段。我比较怀疑定义这个标准的人有没有写过 API。
我个人常用的是风格 3,我认为所有数据库模型都是平级的对象,都可以通过 id 进行增删改查,即便在逻辑上他们是有层级关系的,但是这个层级关系并不需要显示的体现在 API 上,更不需要体现在 URL 上,只需要在代码逻辑上保证业务逻辑就可以了。
我就想问问,你们在写 API 的时候是怎么定义 URL 的?
我觉着作者定义这个标准其实就是想更好地管理资源,虽然有时候 restful 确实冗余信息太多。比如你所说的,我们都知道动物是属于动物园里面,或者说动物园数量很少的时候我们或许直接采用
/zoos/animals/:id
甚至是
/animal/:id
就足够了,但是有的资源哪怕在数据库里是平级关系但是他们的父级资源是数量是很庞大的,比如我有 1000 篇文章,然后每篇文章都有 1000 个评论。那么 api 写成
posts/999/comments/100
个人感觉就会比
/comments/100
更有层次感,可读性更高也能够清晰的知道当前资源的归属地,这条评论是属于 id 为 999 的文章的,这对于开发而言是有好处的。
如果加上 slug 就更清晰了
/posts/post-about-docker/comments/100
对 restful 来说第一种场景是冗余,但是在第二种场景我觉得是很恰当的用途,往往现实生活中第二种场景应该会比较多。具体要怎么定我觉得还是分情况来看吧开发者自行权衡。Rails 也只是给了我们一个通用的约定,让我们更容易地去定义 restful 的 API,但并没有限制我们的手脚,我们依然可以很容易地自定义自己想要的路由。虽然听起来不符合规范,不过你说的三种我都有在项目中用到。
嵌套资源的依赖是一层层的,你不可能直接拉到最后的,举个例子:
#你永远应该
current_user.credit_cards.find(params[:id])
#而不应该
CreditCard.find(params[:id])
#假如你的url是 /account/credit_cards/1/addresses,你应该这么取用
current_user.credit_cards.find(params[:credit_card_id]).addresses
嵌套资源一般而言是每层都要取出来的,因为你拿到一个 address 没有办法知道他是不是 current_user 拥有的,除非把 credit_card 也查出来。
current_user 我们一般是通过 Token 来获取的,不会把用户 id 放到 URL 中,这样太容易伪造了。所以我还是觉得在涉及权限的 API 中,account 是没有必要放到 URL 中的。URL 中应该只出现需要的参数,前端使用的时候也会方便很多。
你需要仔细再看看@mizuhashi举的例子,他的例子里 params[:id] 是 credit_card 的 id,不是 user 或 accout 的 id。他所说的情况是 user 没有跟内层资源直接关联,而是跟外层资源关联。
我并没有把 id 放到 url 中,account 只是个虚拟的单数资源,本体是 current_user。
既然你已经意识到了资源取用是要先鉴权的,那么假如你用/addresses/1 取用一个地址,而地址根本没有和 user 关联,你要怎么鉴权?head 404 unless @address.credit_card.user_id == current_user.id
?如果是多对多关联你是不是还要把所有查出来然后对集合 find?如果业务逻辑需要操作多对多关联,你要怎么确定操作哪个?
知道每一级资源的 id,在链条上有多对多关联的时候是必须的,在仅有一对多关联的时候是推荐的。
我觉的都没毛病,关键看 API 是给谁用的,API 使用者觉的方便好用不
REST 的好处是熟悉 REST 的开发者对于类似的 API 上手快一些
还有 API 的整体设计统一也很重要
比如取得一个动物园的所有动物
可以这样
animals?zoos_id=:id
这个时候获取一个动物详情,animal/:id 相对比较自然
也可以这样
/zoos/:id/animals
这个时候获取一个动物详情,/zoos/:id/animals/:id 相对比较自然
首先我更支持平级非嵌套的资源表示方式,即 /animals/:id
,如果要限定 zoo 下的 animals 可以用 /animals?zoo_id=1
。但在有一个唯一标识符 (ID) 的情况下就没必要嵌套了。用户访问他看不到的资源应该通过其他方式来限制。
其次建议 LZ 多了解下 RESTful API 的定义。不是 Rails 的那套 DSL 定义出来路由的才叫 RESTful。嵌套资源也不是所谓的标准风格。理论上只要用统一的 URL 表示资源就可以算是 RESTful 的,就算你用的是 /animals?id=1
。它是一种架构风格但不是规范。而资源有个统一的 URL 的好处是它的常规操作只用修改 method 而不是 URL + method。并且这种统一风格可以让大多数资源受益。
最后说一句,如何设计好的 API 跟内部怎么实现是两码事。
我没有说 Restful 一定要用多层结构,我只是说 Restful 规范是这么定的。麻烦你看清楚点再来说。另外,我有下什么结论吗?
我只是说 RESTful 就特么只是一个风格,不是一个规范,麻烦你看清楚点再来说。
RESTful 也没有规定要用多层结构。
RESTful 提到的例子本来就是你说的第三种做法,冗余个蛋蛋。
你有没有写过 API 啊?懂不懂什么叫 RESTful 啊?我替你捉急啊。
回到正题,这帖子隔壁 Stackoverflow 就有,我看了答案,里面也写得很详细。先不说你为什么没直接去查那边,而是直接过来开贴问了。
首先 RESTful 没有规定任何嵌套结构。打开维基百科 RESTful页面自己搜 nested,根本就没有。嵌套结构完全是你自己决定要不要加。
Nested resource 是Rails引入的。当然你可以不用,没人拦着。
至于为什么要有 Nested resource,很简单。你例子里只举了show
方法,映射到/animal/:id
。那么list
方法呢?特别是按照 zoo 来 list,你怎么做?不还是要访问/zoo/:zoo_id/animals
吗?那不是平白无故搞出两个路由来?
当然,如果你觉得同时搞出
/zoo/:zoo_id/animals
/animal/:id
没什么不舒服的话,当然可以这么用。的确也有一些风格是建议保持最小结构的,比如Azure 的 API 风格建议就说(很显然,他们也用的 RESTful): "Instead, try to keep URIs relatively simple. The preceding query can be replaced with the URI /customers/1/orders
to find all the orders for customer 1, and then /orders/99/products
to find the products in this order."
最后再重申一次,这种嵌套结构的采用与否,和 RESTful 没有一毛钱的关系。