REST嵌套资源的最佳实践是什么?

时间:2014-01-06 13:51:01

标签: rest api-design

据我所知,每个资源都应该只有一个规范路径。因此,在下面的示例中,优秀的URL模式是什么?

以公司的休息代表为例。在此假设示例中,每个公司拥有 0个或更多部门,每个部门拥有 0个或更多个员工。

没有关联公司的部门不能存在

没有相关部门的员工不能存在

现在我找到资源模式的自然表示。

  • /companies 公司集合 - 接受新公司的接受。获取整个系列。
  • /companies/{companyId}一家公司。接受GET,PUT和DELETE
  • /companies/{companyId}/departments接受新项目的POST。 (在公司内部创建一个部门。)
  • /companies/{companyId}/departments/{departmentId}/
  • /companies/{companyId}/departments/{departmentId}/employees
  • /companies/{companyId}/departments/{departmentId}/employees/{empId}

考虑到约束,在每个部分中,我觉得如果有点深深嵌套,这是有道理的。

但是,如果我想列出(GET)所有公司的所有员工,我的困难就来了。

该资源模式最贴切地映射到/employees(所有员工的集合)

这是否意味着我应该/employees/{empId},因为如果是这样,那么有两个URI可以获得相同的资源?

或者整个架构可能会被展平,但这意味着员工是一个嵌套的顶级对象。

在基本级/employees/?company={companyId}&department={deptId}返回完全相同的员工视图,作为最深层嵌套的模式。

其他资源拥有的网址格式的最佳做法是什么,但应该可单独查询?


更新:请参阅下面的回答,看看我做了什么。

7 个答案:

答案 0 :(得分:138)

I've tried both design strategies - nested and non-nested endpoints. I've found that:

  1. if the nested resource has a primary key and you don't have its parent primary key, the nested structure requires you to get it, even though the system doesn't actually require it.

  2. nested endpoints typically require redundant endpoints. In other words, you will more often than not, need the additional /employees endpoint so you can get a list of employees across departments. If you have /employees, what exactly does /companies/departments/employees buy you?

  3. nesting endpoints don't evolve as nicely. E.g. you might not need to search for employees now but you might later and if you have a nested structure, you have no choice but to add another endpoint. With a non-nested design, you just add more parameters, which is simpler.

  4. sometimes a resource could have multiple types of parents. Resulting in multiple endpoints all returning the same resource.

  5. redundant endpoints makes the docs harder to write and also makes the api harder to learn.

In short, the non-nested design seems to allow a more flexible and simpler endpoint schema.

答案 1 :(得分:127)

你所做的是正确的。通常,同一资源可能有许多URI - 没有规则表明您不应该这样做。

通常,您可能需要直接访问项目或作为其他内容的子集 - 因此您的结构对我来说很有意义。

仅仅因为员工可以在部门下访问:

company/{companyid}/department/{departmentid}/employees

并不意味着他们也无法在公司下访问:

company/{companyid}/employees

哪会让该公司的员工回归。这取决于您的消费客户需要什么 - 这就是您应该设计的内容。

但我希望所有URL处理程序使用相同的支持代码来满足请求,这样您就不会复制代码。

答案 2 :(得分:66)

我已经将我从问题所做的事情转移到了更多人可能会看到它的答案。

我所做的是在嵌套端点上设置创建端点,用于修改或查询项目的规范端点不在嵌套资源。< / p>

因此在此示例中(仅列出更改资源的端点)

  • POST /companies/创建新公司,返回指向已创建公司的链接。
  • POST /companies/{companyId}/departments在放置部门时创建新部门会返回指向/departments/{departmentId}的链接
  • PUT /departments/{departmentId}修改部门
  • POST /departments/{deparmentId}/employees创建新员工,返回指向/employees/{employeeId}的链接

因此每个集合都有根级资源。但创建位于拥有对象中。

答案 3 :(得分:18)

我已经阅读了上述所有答案,但似乎没有共同的策略。我发现了一篇关于best practices in Design API from Microsoft Documents的好文章。我想你应该参考。

  

在更复杂的系统中,提供URI可能很诱人   使客户能够浏览多个级别的关系,   例如/customers/1/orders/99/products.但是,这个级别   复杂性可能难以维持,如果不复杂则不灵活   资源之间的关系在未来发生变化。 相反,请尝试   保持URI相对简单 。一旦应用程序引用了a   资源,应该可以使用此引用来查找项目   与该资源有关。上述查询可以替换为   URI /customers/1/orders用于查找客户1的所有订单,以及   然后/orders/99/products按此顺序查找产品。

  

提示

     

避免要求资源URI比复杂URI更复杂   collection/item/collection

答案 4 :(得分:8)

您的网址外观与REST无关。什么都可以。它实际上是一个“实现细节”。就像你如何命名变量一样。它们必须具有独特性和耐用性。

不要在此浪费太多时间,只需做出选择并坚持下去/保持一致。例如,如果您使用层次结构,则可以为所有资源执行此操作。如果您使用查询参数...等,就像代码中的命名约定一样。

为什么这样?据我所知,“RESTful”API可以浏览(你知道......“超媒体作为应用程序状态的引擎”),因此API客户端不关心你的URL是什么样的,只要它们是有效(没有SEO,没有人需要阅读那些“友好的网址”,除了可能用于调试...)

作为API开发人员,而不是API客户端,您对REST API中的URL有多么好/可理解,您的代码中的变量名称也是如此。

最重要的是,您的API客户端知道如何解释您的媒体类型。 例如,它知道:

  • 您的媒体类型有一个链接属性,列出可用/相关链接。
  • 每个链接都由关系标识(就像浏览器知道链接[rel =“stylesheet”]表示它的样式表或rel = favico是指向图标的链接...)
  • 它知道这些关系是什么意思(“公司”是指公司列表,“搜索”是指在资源列表上进行搜索的模板化网址,“部门”是指当前资源的部门)

下面是一个示例HTTP交换(实体是yaml,因为它更容易编写):

请求

GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml

回复:指向主要资源(公司,人员,等等......)的链接列表

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: this is your API's entrypoint (like a homepage)  
links:
  # could be some random path https://api.acme.local/modskmklmkdsml
  # the only thing the API client cares about is the key (or rel) "companies"
  companies: https://api.acme.local/companies
  people: https://api.acme.local/people

请求链接到公司(使用之前回复的body.links.companies)

GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml

响应:部分公司列表(在项目下),资源包含相关链接,比如获取下一对公司的链接(body.links.next)另一个(模板化)链接搜索(body.links.search)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: representation of a list of companies
links:
  # link to the next page
  next: https://api.acme.local/companies?page=2
  # templated link for search
  search: https://api.acme.local/companies?query={query} 
# you could provide available actions related to this resource
actions:
  add:
    href: https://api.acme.local/companies
    method: POST
items:
  - name: company1
    links:
      self: https://api.acme.local/companies/8er13eo
      # and here is the link to departments
      # again the client only cares about the key department
      department: https://api.acme.local/companies/8er13eo/departments
  - name: company2
    links:
      self: https://api.acme.local/companies/9r13d4l
      # or could be in some other location ! 
      department: https://api2.acme.local/departments?company=8er13eo

因此,当您看到链接/关系方式如何构建路径部分时,您的网址对您的API客户端没有任何价值。如果您正在将您的URL结构作为文档传达给您的客户端,那么您不会执行REST(或者至少不是“Richardson's maturity model”的第3级)

答案 5 :(得分:7)

我不同意这种道路

GET /companies/{companyId}/departments

如果你想获得部门,我认为最好使用/ departments资源

GET /departments?companyId=123

我想你有一个companies表和一个departments表,然后用你用你编程语言映射它们的类。我还假设部门可以连接到除公司之外的其他实体,因此/部门资源很简单,将资源映射到表很方便,而且您不需要那么多的端点,因为您可以重用

GET /departments?companyId=123

用于任何类型的搜索,例如

GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.

如果您想创建一个部门,

POST /departments
应使用

资源,请求正文应包含公司ID(如果部门只能链接到一家公司)。

答案 6 :(得分:1)

Rails为此提供了解决方案:shallow nesting

我认为这是一个好习惯,因为当您直接处理已知资源时,无需使用嵌套路由,如此处其他答案所述。