首页 文章

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

提问于
浏览
214

据我所知,每个资源都应该有 only one canonical 路径 . 因此,在下面的示例中,良好的URL模式是什么?

以公司的休息代表为例 . 在这个假设的例子中,每个公司 owns 0或更多部门和每个部门 owns 0或更多员工 .

没有关联公司的部门 can't exist .

没有关联部门的员工 can't exist .

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

  • /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} 返回与最深层嵌套模式完全相同的员工视图 .

对于其他资源资源是 owned 但应该可以单独查询的URL模式的最佳做法是什么?


UPDATE: 请参阅下面的答案,看看我做了什么 .

6 回答

  • 53

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

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

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

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

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

    company/{companyid}/employees

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

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

  • 107

    我尝试了两种设计策略 - 嵌套和非嵌套 endpoints . 我发现:

    • 如果嵌套资源具有主键且您没有其主键,则嵌套结构要求您获取它,即使系统实际上并不需要它 .

    • 嵌套 endpoints 通常需要冗余 endpoints . 换句话说,您通常需要额外的/员工 endpoints ,以便获得跨部门的员工列表 . 如果您有/员工,/公司/部门/员工到底会给您带来什么?

    • 嵌套 endpoints 不会很好地发展 . 例如 . 您现在可能不需要搜索员工,但是您可能以后也可以,如果您有嵌套结构,则别无选择,只能添加另一个 endpoints . 使用非嵌套设计,您只需添加更多参数,这更简单 .

    • 有时资源可能有多种类型的父母 . 导致多个 endpoints 都返回相同的资源 .

    • 冗余 endpoints 使得文档更难写,也使得api更难学习 .

    简而言之,非嵌套设计似乎允许更灵活和更简单的 endpoints 模式 .

  • 6

    我把我从问题所做的事情转移到了更多人可能会看到它的答案 .

    我所做的是在嵌套 endpoints 上创建创建 endpoints ,修改或查询项目的规范 endpoints 是 not at the nested resource .

    所以在这个例子中(只列出更改资源的 endpoints )

    • POST /companies/ 创建新公司返回创建公司的链接 .
      放置部门时

    • POST /companies/{companyId}/departments 创建新部门会返回指向 /departments/{departmentId} 的链接

    • PUT /departments/{departmentId} 修改了一个部门

    • POST /departments/{deparmentId}/employees 创建新员工返回指向 /employees/{employeeId} 的链接

    因此,每个集合都有根级资源 . 但是 createowning 对象中 .

  • 5

    我不同意这种道路

    GET /companies/{companyId}/departments
    

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

    GET /departments?companyId=123
    

    我想你有一个 companies 表和一个 departments 表然后用你用你编程语言映射它们的类 . 我还假设部门可以附加到除公司之外的其他实体,因此/部门资源很简单,因为您可以重用,所以需要尽可能多的 endpoints

    GET /departments?companyId=123
    

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

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

    如果你想创建一个部门,那么

    POST /departments
    

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

  • 107

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

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

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

    How nice/understandable a URL is in a REST API is only interesting to you as the API developer, not the API client, as would the name of a variable in your code be.

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

    • 您的媒体类型有一个链接属性,列出可用/相关链接 .

    • 每个链接都由关系标识(就像浏览器知道链接[rel = "stylesheet"]表示其样式表或rel = favico是指向图标的链接...)

    • 并且它知道这些关系意味着什么("companies"表示公司列表,"search"表示在资源列表上进行搜索的模板化URL,"departments"表示当前资源的部门)

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

    Request

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

    Response: 主要资源的链接列表(公司,人员,等等......)

    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
    

    Request: 链接到公司(使用以前的回复的body.links.companies)

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

    Response: 公司的部分清单(在项目下),资源包含相关链接,如获取下一对公司的链接(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客户端没有任何 Value . 如果您正在将URL的结构作为文档传达给客户端,那么您不会使用REST(或者至少不是“Richardson's maturity model”的3级)

  • 6

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

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

    .

    提示避免要求资源URI比集合/项目/集合更复杂 .

相关问题