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

时间:2012-11-21 07:57:46

标签: rest api-design

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

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

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

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

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

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

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

6 个答案:

答案 0 :(得分:17)

  

这是有问题的,因为用户所属的不再明显   对某个公司。

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

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

e.g。用户名:

company/foo/user/bar

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

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

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

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

答案 1 :(得分:15)

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

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

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

答案 2 :(得分:6)

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

从概念角度看,如果employee只能存在于company关系中,则employee被建模为composition。因此company只能通过employee识别。现在数据库发挥作用,所有when a resource could not exist without another, it should be represented as its sub-resource行都获得唯一标识符。

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

回答你的额外问题

  

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

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

  

如果没有其他资源,我该如何表示无法识别资源?

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

  

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

子资源非常适合于合成,但更一般地说是为了模拟资源在没有父资源的情况下不能存在并始终属于一个父资源。您的规则{{1}}是此决定的良好指导。

答案 3 :(得分:4)

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

答案 4 :(得分:1)

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

  

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

     

/ person / {peopleId} - &gt;返回有关此人的数据

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

答案 5 :(得分:0)

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

假设您想知道您使用的ira / rsp /养老金的EmployerContribution/companies/enron/users/fred/EmployerContribution 您将获得由enron(或$ 0)贡献的特定金额。

如果您希望来自任何或所有公司的EmployerContribution fred工作(编辑)怎么办?
您不需要具体公司才能理解。 /companies/any/employee/fred/EmployerContribution

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

您甚至可以将抽象更改为代表过去10年Fred所聘用的所有公司。 /companies/last10years/employee/fred/EmployerContribution