RESTful设计:何时使用子资源?

Gil*_*ili 54 rest api-design

在设计资源层次结构时,何时应该使用子资源?

我曾经认为,当一个资源不能存在而没有另一个资源时,它应该被表示为它的子资源.我最近跑过这个反例:

  • 所有公司的员工都是唯一可识别的.
  • 员工的访问控制和生命周期取决于公司.

我把它建模为: /companies/{companyName}/employee/{employeeId}

请注意,我不需要查找公司以找到员工,我也应该这样做?如果我这样做,我付出代价来查找我不需要的信息.如果我不这样做,这个URL错误地返回HTTP 200:

/companies/{nonExistingName}/employee/{existingId}

  1. 我应该如何表示属于另一个资源的事实?
  2. 我应该如何表示没有另一个资源无法识别资源的事实?
  3. 子资源意味着什么关系,而不是模型?

Dou*_*rop 17

这是有问题的,因为用户属于特定公司不再明显.

有时,这可能会突出显示您的域模型的问题.为什么用户属于公司?如果我改变公司,我是全新的人吗?如果我在两家公司工作怎么办?我是两个不同的人吗?

如果答案是肯定的,那么为什么不采用一些公司唯一标识符来访问用户呢?

例如用户名:

company/foo/user/bar

(bar我的用户名在该特定公司名称空间内是唯一的)

如果答案是否定的,那么为什么我自己不是用户(人),而且这个company/users集合只是指向我:( <link rel="user" uri="/user/1" />注意:员工似乎更合适)

现在你的具体的例子之外,我认为,当涉及到资源子资源的关系更适合使用,而不是所有权(这就是为什么你与识别一个公司的隐含标识企业用户的冗余挣扎).

我的意思是说users实际上是一个企业资源的子资源,因为使用的是定义一个企业与员工之间的关系-说的另一种方式是:你必须定义一个公司,然后才能开始雇用员工.同样,必须先定义(出生)用户(人)才能招募用户.

  • 我希望你的回答更简洁,但无论如何你都要把它钉在上面.关键是要定义:`/ companies/{companyName}/users`和`/ users/{id}`因为查找与公司关联的用户需要`{companyName}`但查找单个用户不会,因此用户是一个顶级资源.谢谢! (6认同)
  • @Daniel,这不是多余的.当有人请求`HTTP GET/companies/{companyName}/users/{id}`时,你应该返回`HTTP 303("See Other")`指向`/ users/{id}`.前者是别名.后者是规范的URI. (6认同)
  • 我甚至不知道重定向是多么必要.服务器控制URI,客户端应该不知道.如果服务器想要在那时提供直接表示,它可以.除了可能的缓存未命中之外,它对客户端没有任何影响. (3认同)

Gil*_*ili 15

一年后,我以下面的妥协结束(对于包含唯一标识符的数据库行):

  1. 在根目录(例如/companies/{id}/employees/{id})为所有资源分配规范URI .
  2. 如果资源不能存在而没有另一个资源,则应将其表示为其子资源; 但是,将操作视为搜索引擎查询.意思是,不是立即执行操作,只需返回HTTP 307 ("Temporary redirect")指向规范URI.这将导致客户端针对规范URI重复操作.
  3. 您的规范文档应该只显示与您的概念模型匹配的根资源(不依赖于实现细节).实施细节可能会发生变化(您的行可能不再是唯一可识别的),但您的概念模型将保持不变.在上面的例子中,你会告诉客户/companies但不是/employees.

这种方法有以下好处:

  1. 它消除了进行不必要的数据库查找的需要.
  2. 它将每次请求的健全性检查次数减少到一次.最多,我必须检查员工是否属于公司,但我不再需要进行两次验证检查/companies/{companyId}/employees/{employeeId}/computers/{computerId}.
  3. 它对数据库可伸缩性产生了混合影响.一方面,您通过锁定较少的表来减少锁争用,持续时间较短.但另一方面,您正在增加死锁的可能性,因为每个根资源必须使用不同的锁定顺序.我不知道这是否是净收益或损失,但我感到安慰的是,无论如何都无法阻止数据库死锁,并且最终的锁定规则更容易理解和实现.如有疑问,请选择简单.
  4. 我们的概念模型保持不变.通过确保规范文档仅公开我们的概念模型,我们可以在不破坏现有客户端的情况下自由删除包含实现细节的URI.请记住,只要您的规范将其结构声明为未定义,就没有什么能阻止您在中间URI中公开实现细节.


sai*_*ama 6

您决定是否应将资源建模为子资源的规则是有效的.您的问题不是来自错误的概念模型,而是让您的数据库模型泄漏到REST模型中.

从概念上看,employee如果它只能存在于company关系中,则将其建模为合成.将employee可因此仅通过鉴定company.现在数据库发挥作用,所有employee行都获得唯一标识符.

我的建议是不要让您的概念模型中的数据库模型泄露,因为您将基础架构问题暴露给您的API.例如,当您决定切换到像MongoDB这样的面向文档的数据库时会发生什么情况,您可以将员工作为公司文档的一部分进行建模,而不再具有这种人为的唯一ID?你想改变你的API吗?

回答你的额外问题

我应该如何表示属于另一个资源的事实?

通过子资源组成,通过URL链接进行其他关联.

我应该如何表示没有另一个资源无法识别资源的事实?

在资源URL中使用这两个id值,并通过检查"组合"是否存在,确保不要让数据库泄漏到API中.

子资源意味着什么关系,而不是模型?

子资源非常适合于合成,但更一般地说是在没有父资源的情况下资源不能存在并且总是属于一个父资源的模型.您的规则when a resource could not exist without another, it should be represented as its sub-resource是对此决定的良好指导.

  • 几年前我尝试过玩这个游戏,但我不再尝试设计与数据库无关的软件.成本/收益根本不值得,看看我们很少更改数据库和一些实现细节*总是*泄漏(例如,并非所有数据库都使用整数ID).您可以使用当前数据库尽可能做到最好.作为奖励,您最终会得到更简单的代码.最后一点:通过将原始id存储在新数据库模式中,我总是可以在迁移后保留向后兼容性. (6认同)

小智 6

如果子资源在没有其所属实体的情况下是唯一可识别的,则它不是子资源,并且应该有自己的命名空间(即 /users/{user} 而不是 /companies/{*}/users/{user})。最重要的是:永远不要使用实体的数据库主键作为资源标识符。这是最常见的错误,因为实施细节会泄露给外界。您应该始终拥有一个自然的业务密钥(例如用户名或公司编号,而不是用户 ID 或公司 ID)。如果您愿意,可以通过唯一约束来强制这种键的唯一性,但是实体的主键永远不应该离开应用程序的持久层,或者至少它不应该成为任何服务的参数方法。如果遵循此规则,则区分组合 (/companies/{company}/users/{user}) 和关联 (/users/{user}) 应该不会有任何问题,因为如果您的子资源没有一个自然的业务密钥,可以在全局上下文中识别它,您可以非常确定它确实是一个依赖子资源(或者您必须首先创建一个业务密钥以使其可全局识别)。

  • 我不同意你的观点,原因如下: 1. 社区最初放弃自然业务密钥的原因是它们随着时间的推移而发生变化。2. 如果您使用非业务密钥,则它没有理由更改(即使您更改数据库)。3.自然键无法实现幂等操作。有人可以在删除并创建新的“Nintendo”的同时调用“PUT /companies/Nintendo/”。如果第一个客户端重试“PUT”操作(幂等性),他无法检测到底层实例已更改。 (16认同)