63

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

我曾经相信,当一个资源没有另一个资源就无法存在时,它应该被表示为它的子资源。我最近遇到了这个反例:

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

我将其建模为:/companies/{companyName}/employee/{employeeId}

请注意,我不需要查找公司来查找员工,我应该这样做吗?如果我这样做了,我就要付出代价来查找我不需要的信息。如果我不这样做,此 URL 会错误地返回 HTTP 200:

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

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

6 回答 6

19

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

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

这种方法有以下好处:

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

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

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

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

例如用户名:

company/foo/user/bar

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

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

现在在您的具体示例之外,我认为资源-子资源关系在使用而不是所有权方面更合适(这就是为什么您正在努力为隐含识别公司的用户识别公司的冗余)。

我的意思是,users其实是一个公司资源的子资源,因为用途是定义一个公司和它的员工之间的关系——换一种说法就是:你必须先定义一个公司,然后才能开始雇用员工。同样,必须先定义(出生)用户(人),然后才能招募他们。

于 2012-11-21T18:29:33.710 回答
9

您决定是否应将资源建模为子资源的规则是有效的。您的问题不是由错误的概念模型引起的,而是您将数据库模型泄漏到 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是这个决定的一个很好的指导。

于 2012-11-22T09:03:12.717 回答
4

如果子资源在没有其拥有实体的情况下是唯一可识别的,则它不是子资源并且应该有自己的命名空间(即 /users/{user} 而不是 /companies/{*}/users/{user})。最重要的是:永远不会使用实体的数据库主键作为资源标识符。这是实现细节泄露给外界的最常见错误。您应该始终拥有一个自然的业务密钥(例如用户名或公司编号,而不是用户 ID 或公司 ID)。如果您愿意,可以通过唯一约束来强制执行此类键的唯一性,但是实体的主键永远不应该离开应用程序的持久层,或者至少它不应该成为任何服务的参数方法。如果你遵守这条规则,你应该'

于 2014-03-18T21:56:32.090 回答
2

这是解决这种情况的一种方法:

/companies/{companyName}/employee/{employeeId} -> 返回有关员工的数据,还应包括该人的数据

/person/{peopleId} -> 返回有关此人的数据

谈论员工而不谈论公司是没有意义的,但是即使没有公司,即使他被多家公司雇用,谈论这个人也确实有意义。一个人的存在与他是否被任何公司雇用无关,但就业的存在确实取决于公司。

于 2013-09-02T12:25:36.800 回答
0

问题似乎是没有特定的公司,但员工在技术上属于某个公司或组织,否则他们可能被称为流浪汉或政客。成为一名员工意味着在某处存在公司/组织关系,但不是特定的关系。员工也可以为多个公司/组织工作。当需要特定的公司背景时,您的原创作品/companies/{companyName}/users/{id}

假设您想知道EmployerContribution您将使用的 ira/rsp/pension: /companies/enron/users/fred/EmployerContribution 您将获得 enron 贡献的特定金额(或 0 美元)。

如果您想要EmployerContribution来自任何或所有公司的 s,fred 工作(ed)怎么办?
你不需要一个具体的公司来让它有意义。/companies/any/employee/fred/EmployerContribution

当员工的公司无关紧要但作为员工重要时,“任何”显然是一个抽象或占位符。您需要拦截“公司”处理程序以防止数据库查找(尽管不确定为什么公司不会被缓存?可以有多少?)

您甚至可以更改抽象以表示像 Fred 在过去 10 年中受雇的所有公司。 /companies/last10years/employee/fred/EmployerContribution

于 2016-11-13T17:42:54.033 回答