【问题标题】:REST API - Bulk Create or Update in single request [closed]REST API - 在单个请求中批量创建或更新 [关闭]
【发布时间】:2015-02-19 00:33:44
【问题描述】:

假设有两个资源BinderDoc 具有关联关系,这意味着DocBinder 独立存在。 Doc 可能属于也可能不属于Binder,而Binder 可能为空。

如果我想设计一个允许用户发送Docs 集合的 REST API,IN A SINGLE REQUEST,如下所示:

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}

对于docs 中的每个文档,

  • 如果存在doc,则将其分配给Binder
  • 如果doc 不存在,创建它然后分配它

我真的很困惑应该如何实现:

  • 使用什么 HTTP 方法?
  • 必须返回什么响应代码?
  • 这甚至符合 REST 条件吗?
  • URI 看起来如何? /binders/docs?
  • 处理批量请求时,如果一些项目引发错误但其他项目通过怎么办。必须返回什么响应代码?批量操作应该是原子操作吗?

【问题讨论】:

    标签: api rest http


    【解决方案1】:

    我认为您可以使用 POST 或 PATCH 方法来处理此问题,因为它们通常为此设计。

    • 使用POST 方法 通常用于在列表资源上添加元素,但您也可以支持此方法的多个操作。请参阅此答案:Update an entire resource collection in a REST way。您还可以支持输入的不同表示格式(如果它们对应于数组或单个元素)。

      在这种情况下,没有必要定义您的格式来描述更新。

    • 使用PATCH 方法 也是合适的,因为相应的请求对应于部分更新。根据 RFC5789 (https://www.rfc-editor.org/rfc/rfc5789):

      一些扩展超文本传输​​协议 (HTTP) 的应用程序需要一项功能来进行部分资源修改。现有的 HTTP PUT 方法只允许完全替换文档。此提案添加了一个新的 HTTP 方法 PATCH,以修改现有的 HTTP 资源。

      在这种情况下,您必须定义格式来描述部分更新。

    我认为在这种情况下,POSTPATCH 非常相似,因为您实际上不需要描述对每个元素执行的操作。我会说这取决于要发送的表示形式。

    PUT 的情况不太清楚。事实上,当使用PUT 方法时,您应该提供整个列表。事实上,请求中提供的表示将替换列表资源之一。

    关于资源路径,您可以有两种选择。

    • 使用文档列表的资源路径

    在这种情况下,您需要在请求中提供的表示中明确地提供带有活页夹的文档链接。

    这是此/docs 的示例路线。

    这种方法的内容可以是方法POST

    [
        { "doc_number": 1, "binder": 4, (other fields in the case of creation) },
        { "doc_number": 2, "binder": 4, (other fields in the case of creation) },
        { "doc_number": 3, "binder": 5, (other fields in the case of creation) },
        (...)
    ]
    
    • 使用 binder 元素的子资源路径

    此外,您还可以考虑利用子路由来描述文档和活页夹之间的链接。现在不必在请求内容中指定有关文档和活页夹之间关联的提示。

    这是此/binder/{binderId}/docs 的示例路线。在这种情况下,如果文档不存在,则使用 POSTPATCH 方法发送文档列表将在创建文档后将文档附加到标识符为 binderId 的活页夹。

    这种方法的内容可以是方法POST

    [
        { "doc_number": 1, (other fields in the case of creation) },
        { "doc_number": 2, (other fields in the case of creation) },
        { "doc_number": 3, (other fields in the case of creation) },
        (...)
    ]
    

    关于响应,您可以定义响应级别和要返回的错误。我看到两个级别:状态级别(全局级别)和有效负载级别(更薄的级别)。您还可以定义与您的请求对应的所有插入/更新是否必须是原子的。

    • 原子

    在这种情况下,您可以利用 HTTP 状态。如果一切顺利,您将获得状态200。如果没有,如果提供的数据不正确(例如 binder id 无效)或其他情况,则返回另一个状态,例如 400

    • 非原子

    在这种情况下,将返回状态200,并由响应表示来描述已完成的操作以及最终发生错误的位置。 ElasticSearch 在其 REST API 中有一个用于批量更新的端点。这可能会给你一些关于这个级别的想法:http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html

    • 异步

    您还可以实现异步处理来处理提供的数据。在这种情况下,HTTP 状态返回将是202。客户端需要拉取额外的资源来查看会发生什么。

    在完成之前,我还想注意 OData 规范解决了有关实体之间关系的问题,该功能名为导航链接。也许你可以看看这个;-)

    以下链接也可以帮助您:https://templth.wordpress.com/2014/12/15/designing-a-web-api/

    希望对你有帮助 蒂埃里

    【讨论】:

    • 我有后续的问题。我选择了没有嵌套子资源的平面路线。要获取所有文档,我调用GET /docs 并检索特定活页夹中的所有文档,GET /docs?binder_id=x。要删除资源的子集,我应该调用 DELETE /docs?binder_id=x 还是应该在请求正文中使用 {"binder_id": x} 调用 DELETE /docs?您会使用PATCH /docs?binder_id=x 进行批量更新,还是只使用PATCH /docs 并传递对?
    【解决方案2】:

    您可能需要使用 POST 或 PATCH,因为更新和创建多个资源的单个请求不太可能是幂等的。

    PATCH /docs 绝对是一个有效的选择。您可能会发现在您的特定场景中使用标准补丁格式很棘手。不确定。

    你可以使用 200。你也可以使用207 - Multi Status

    这可以通过 RESTful 方式完成。在我看来,关键是拥有一些旨在接受一组文档以更新/创建的资源。

    如果您使用 PATCH 方法,我认为您的操作应该是原子的。即我不会使用 207 状态代码,然后在响应正文中报告成功和失败。如果您使用 POST 操作,那么 207 方法是可行的。您必须设计自己的响应主体来传达哪些操作成功,哪些失败。我不知道有标准化的。

    【讨论】:

    • 非常感谢。 This can be done in a RESTful way 的意思是更新和创建必须分开完成吗?
    • @norbertpy 对资源执行某种写入操作可能会导致其他资源从单个请求中更新和创建。 REST 对此没有任何问题。我选择的短语是因为一些框架通过将 HTTP 请求序列化为多部分文档,然后将序列化的 HTTP 请求作为批处理发送来实现批量操作。我认为这种方法违反了资源识别 REST 约束。
    【解决方案3】:

    PUT ing

    PUT /binders/{id}/docs 创建或更新单个文档并将其关联到活页夹

    例如:

    PUT /binders/1/docs HTTP/1.1
    {
      "docNumber" : 1
    }
    

    补丁 ing

    PATCH /docs 如果文档不存在,则创建它们并将它们与活页夹相关联

    例如:

    PATCH /docs HTTP/1.1
    [
        { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } },
        { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } },
        { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } }
    ] 
    

    稍后我会提供其他见解,但与此同时,如果您愿意,请查看 RFC 5789RFC 6902 和 William Durand 的 Please. Don't Patch Like an Idiot 博客条目。

    【讨论】:

    • 有时客户端需要批量操作,它不想关心资源是否存在。正如我在问题中所说,客户想要发送一堆docs 并将它们与binders 相关联。如果它们不存在,客户想要创建绑定器,如果它们存在则建立关联。在一个 SINGLE BULK 请求中。
    【解决方案4】:

    在我工作的一个项目中,我们通过实现我们称为“批处理”请求的东西解决了这个问题。我们定义了一个路径/batch,我们接受以下格式的json:

    [  
       {
          path: '/docs',
          method: 'post',
          body: {
             doc_number: 1,
             binder: 1
          }
       },
       {
          path: '/docs',
          method: 'post',
          body: {
             doc_number: 5,
             binder: 8
          }
       },
       {
          path: '/docs',
          method: 'post',
          body: {
             doc_number: 6,
             binder: 3
          }
       },
    ]
    

    响应的状态码为 207(多状态),如下所示:

    [  
       {
          path: '/docs',
          method: 'post',
          body: {
             doc_number: 1,
             binder: 1
          }
          status: 200
       },
       {
          path: '/docs',
          method: 'post',
          body: {
             error: {
                msg: 'A document with doc_number 5 already exists'
                ...
             }
          },
          status: 409
       },
       {
          path: '/docs',
          method: 'post',
          body: {
             doc_number: 6,
             binder: 3
          },
          status: 200
       },
    ]
    

    您还可以在此结构中添加对标头的支持。我们实现了一些被证明很有用的东西,即在一批请求之间使用的变量,这意味着我们可以将一个请求的响应用作另一个请求的输入。

    Facebook 和 Google 有类似的实现:
    https://developers.google.com/gmail/api/guides/batch
    https://developers.facebook.com/docs/graph-api/making-multiple-requests

    当您想使用相同的调用创建或更新资源时,我会根据具体情况使用 POST 或 PUT。如果文档已经存在,是否希望整个文档为:

    1. 由您发送的文档替换(即请求中缺少的属性将被删除并覆盖已经存在的属性)?
    2. 与您发送的文档合并(即请求中缺少的属性不会被删除,并且已经存在的属性将被覆盖)?

    如果您想要备选方案 1 的行为,您应该使用 POST PUT,如果您想要备选方案 2 的行为,您应该使用 PUT PATCH。

    http://restcookbook.com/HTTP%20Methods/put-vs-post/

    【讨论】:

    • 喜欢这个概念验证的答案以及 Google 和 Facebook 链接。但不同意关于 POST 或 PUT 的结尾部分。在这个答案提到的两种情况下,第一个应该是 PUT,第二个应该是 PATCH。
    • @RayLuo,你能解释一下为什么除了 POST 和 PUT 之外我们还需要 PATCH 吗?
    • 因为这就是 PATCH 的发明目的。您可以阅读this definition 并查看 PUT 和 PATCH 如何匹配您的 2 个要点。
    • @DavidBerg,Google 似乎更喜欢另一种处理批处理请求的方法,即将每个子请求的标头和正文与主请求的相应部分分开,边界类似于@987654328 @。 Google 和 Facebook 的解决方案之间是否存在一些关键差异?另外,关于“使用一个请求的响应作为另一个请求的输入”,听起来很有趣,你介意分享更多细节吗?或者应该使用哪种场景?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 2021-07-20
    • 2020-07-19
    • 2015-01-18
    • 2012-03-07
    • 1970-01-01
    相关资源
    最近更新 更多