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

据我所知,每个单独的资源都应该有只有一个标准路径。那么在下面的例子中,好的URL模式是什么呢?

以公司的rest表示为例。在这个假设的例子中,每个公司拥有 0个或更多的部门,每个部门拥有 0个或更多的员工。

一个没有关联公司的部门可以不存在

没有相关部门的雇员可以不存在

现在我发现资源模式的自然表示是。

  • /companies 公司的集合接受新公司的POST获取整个集合。
  • 一个独立的公司。接受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}返回与嵌套最深的模式完全相同的员工视图。

对于资源被其他资源拥有但应该可以单独查询的URL模式,最佳实践是什么?

132676 次浏览

你所做的是正确的。一般来说,同一个资源可以有多个uri——没有规则说你不应该这样做。

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

仅仅因为员工在部门下面是可访问的:

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

这并不意味着他们不能在陪伴下访问:

company/{companyid}/employees

这将为该公司带回员工。这取决于你的消费客户需要什么——这就是你应该为之设计的。

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

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

我所做的是在嵌套端点上有创建端点,用于修改或查询项的规范端点是不是在嵌套资源上

在这个例子中(只列出改变资源的端点)

  • POST /companies/创建一个新公司,返回到创建的公司的链接。
  • POST /companies/{companyId}/departments当一个部门被放置时,新部门将返回到/departments/{departmentId}的链接
  • PUT /departments/{departmentId}修改部门
  • POST /departments/{deparmentId}/employees创建一个新员工,返回到/employees/{employeeId}的链接

因此,每个集合都有根级资源。然而,创建拥有对象中。

我不同意这种方式

GET /companies/{companyId}/departments

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

GET /departments?companyId=123

我假设你有一个companies表和一个departments表,然后用你所使用的编程语言来映射它们。我还假设部门可以附加到其他实体,而不是公司,因此/departments资源是直接的,将资源映射到表很方便,而且由于可以重用,您不需要那么多端点

GET /departments?companyId=123

例如,任何类型的搜索

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

如果要创建一个部门,可以使用

POST /departments

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

我尝试了两种设计策略——嵌套端点和非嵌套端点。我发现:

  1. 如果嵌套资源有一个主键,而您没有它的父主键,那么嵌套结构要求您获取它,即使系统实际上并不需要它。

  2. 嵌套端点通常需要冗余端点。换句话说,您通常需要额外的/employees端点,以便获得跨部门的员工列表。如果你有/员工,那么/公司/部门/员工到底会给你买什么?

  3. 嵌套端点不能很好地发展。例如,你现在可能不需要搜索员工,但如果你有一个嵌套结构,你别无选择,只能添加另一个端点。对于非嵌套设计,只需添加更多参数,这更简单。

  4. 有时一个资源可以有多种类型的父节点。导致多个端点都返回相同的资源。

  5. 冗余端点使文档更难编写,也使API更难学习。

简而言之,非嵌套设计似乎允许更灵活、更简单的端点模式。

url的外观与REST无关。任何事情都可能发生。它实际上是一个“实现细节”。就像给变量命名一样。它们所需要的是独特和耐用。

不要在这上面浪费太多时间,只要做出选择并坚持下去。例如,如果你采用层次结构,那么你对所有资源都这样做。如果你使用查询参数…等等,就像代码中的命名约定一样。

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

REST API中的URL有多好/易懂,只对作为API开发人员的你感兴趣,而不是API客户端,就像代码中的变量名一样。

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

  • 您的媒体类型有一个links属性,它列出了可用的/相关的链接。
  • 每个链接都由一个关系来标识(就像浏览器知道link[rel="stylesheet"]意味着它是一个样式表,或者rel=favico是一个指向favicon的链接…)
  • 它知道这些关系的含义(“公司”指的是公司列表,“搜索”指的是在资源列表上进行搜索的模板url,“部门”指的是当前资源的部门)

下面是一个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

回应:一个公司的部分列表(在items下),资源包含相关的链接,比如获取下几个公司的链接(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

你可以看到,如果你用链接/关系的方式构造url的路径部分对API客户端没有任何价值。如果你把你的url结构作为文档传达给你的客户端,那么你就不是在做REST(或者至少不是按照“理查德森成熟度模型”的3级)

我读了上面所有的答案,但似乎他们没有共同的策略。我找到了一篇关于从Microsoft文档设计API的最佳实践的好文章。我认为你应该推荐。

在更复杂的系统中,可能会倾向于提供这样的uri 使客户能够在多个层次的关系中导航, 然而,这个级别的 复杂性可能很难维护,如果 资源之间的关系在未来会发生变化。相反,试着去 保持uri相对简单。类的引用 资源时,应该可以使用此引用查找项目 与该资源相关。说明上述查询可替换为 URI /customers/1/orders来查找客户1的所有订单,并且

,然后/orders/99/products找到这个顺序的产品

提示

避免要求资源uri比 collection/item/collection . < / p >

Rails为此提供了一个解决方案:浅的嵌套

我认为这很好,因为当您直接处理已知资源时,不需要使用嵌套路由,正如在这里的其他回答中所讨论的那样。

根据django rest框架文档:

一般来说,我们建议尽可能使用平面样式的API表示,但如果适度使用嵌套URL样式也可以是合理的。

https://www.django-rest-framework.org/api-guide/relations/#example_2