RESTfuldevise:何时使用子资源?

在devise资源层次时,应该何时使用子资源?

我曾经相信,当一个资源不可能没有另一个时,它应该被表示为它的子资源。 我最近碰到这个反例:

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

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

请注意,我不需要查找公司来find员工,那么我该怎么办? 如果我这样做,我付出代价来查找我不需要的信息。 如果我没有,这个URL错误地返回HTTP 200:

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

  1. 我应该如何表示一个资源属于另一个资源的事实?
  2. 我应该如何expression这样一个事实:一个资源如果没有另一个资源就不能被识别
  3. 子资源意味着什么关系,而不是意味着build模?

这是有问题的,因为用户不属于某个特定的公司。

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

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

例如用户名:

company/foo/user/bar

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

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

现在,除了具体的例子之外,我认为资源 – 子资源关系在使用而不是所有权方面更合适(这就是为什么您要为隐式标识公司的用户识别公司的冗余而苦苦挣扎的原因)。

我的意思是说, users实际上是公司资源的一个子资源,因为用户是用来定义一个公司和员工之间的关系 – 另一种说法是:你必须先定义一个公司,然后才能开始招聘员工。 同样,用户(人)必须被定义(出生),然后才能招募他们。

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

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

这种方法有以下好处:

  1. 它消除了做不必要的数据库查询的需要。
  2. 它将每个请求的理智检查数量减less到一个。 我最多只能检查一个员工是否属于一个公司,但是我不再需要对/companies/{companyId}/employees/{employeeId}/computers/{computerId}进行两次validation检查。
  3. 它对数据库可伸缩性有着混合的影响。 一方面,您可以通过locking较less的表来缩短锁争用时间。 但另一方面,由于每个根资源必须使用不同的locking顺序,因此会增加死锁的可能性。 我不知道这是否是一个净利润或损失,但我感到安慰的是,无论如何数据库死锁无法防止 ,由此产生的locking规则更容易理解和实施。 如有疑问,请select简单。
  4. 我们的概念模型保持完好。 通过确保规范文档仅公开我们的概念模型,我们可以自由地丢弃包含实现细节的URI,而不会破坏现有的客户端。 请记住,只要您的规范将其结构声明为未定义的结构,则不会阻止您公开中间URI中的实现细节。

您决定是否将资源build模为子资源的规则是有效的。 您的问题不是由错误的概念模型引起的,而是让您的数据库模型泄漏到您的REST模型中。

从概念上来看,只有在company关系中才存在的employee才能被模拟为一种组合 。 因此employee只能通过company识别。 现在数据库发挥作用,所有employee行都得到一个唯一的标识符。

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

回答你的额外问题

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

通过子资源构成,其他关联通过URL链接。

我应该如何expression这样一个事实:一个资源如果没有另一个资源就不能被识别?

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

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

子资源非常适合组合,但更普遍地讲,模型认为资源在没有父资源的情况下不能存在,并总是属于一个父资源。 你的规则when a resource could not exist without another, it should be represented as its sub-resource对这个决定是一个很好的指导。

如果一个子资源在没有它自己的实体的情况下是唯一可识别的,那它就不是子资源,并且应该有自己的命名空间(即/ users / {user}而不是/ companies / {*} / users / {user})。 最重要的是:永远都不会使用实体的数据库主键作为资源标识符。 这是实现细节泄漏到外部世界的最常见的错误。 你应该始终有一个自然的商业密钥(如用户名或公司号码,而不是用户名或公司ID)。 如果你愿意的话,这个关键字的唯一性可以通过一个唯一的约束来实现,但是一个实体的主键永远不应该永远离开你的应用程序的持久层,或者至less它不应该成为任何服务的参数方法。 如果你遵循这个规则,你不应该在组合(/ companies / {company} / users / {user})和association(/ users / {user})之间区分,因为如果你的子资源没有一个自然的商业密钥,在全球范围内识别它,你可以非常肯定它是一个依赖的子资源(或者你必须先创build一个商业密钥,使其全球可识别)。

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

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

/ person / {peopleId} – >返回关于此人的数据

谈到员工没有谈到公司,没有道理,但谈论这个人,即使没有公司,即使他被多家公司聘用,也是有道理的。 一个人的存在与他是否被任何公司雇佣无关,但是雇佣的存在取决于公司。

这个问题似乎是在没有特定的公司,但是一个员工在技术上属于某个公司或组织,否则他们可能被称为stream氓或政治家。 作为一名员工意味着一个公司/组织的关系,而不是一个特定的。 此外,员工可以为多个公司/组织工作。 当需要特定的公司上下文时,您的原始作品/companies/{companyName}/users/{id}

假设你想知道你所使用的ira / rsp / pension的/companies/enron/users/fred/EmployerContribution贡献: /companies/enron/users/fred/EmployerContribution你可以得到由enron(或$ 0)贡献的具体金额。

如果你想从任何或所有公司fred works(ed) EmployerContribution的贡献?
你不需要一个具体的公司就可以了。 /companies/any/employee/fred/EmployerContribution

凡是“任何”显然是一个抽象或占位符,当员工的公司没有关系,但作为一名雇员。 你需要拦截“公司”处理程序,以防止数据库查找(虽然不知道为什么公司不会被caching?有多less可以?)

你甚至可以改变这个抽象来代表Fred在过去的十年中所雇用的所有公司。 /companies/last10years/employee/fred/EmployerContribution