REST API - 在单个请求中批量创建或更新

REST API - Bulk Create or Update in single request

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

如果我想设计一个 REST API 允许用户发送 Doc 的集合,在单个请求中,例如以下:

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

对于 docs

中的每个文档

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

PUTing

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

例如:

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

PATCHing

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 5789, RFC 6902 and William Durand's Please. Don't Patch Like an Idiot 博客条目。

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

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

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

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

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

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

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

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

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

    Several applications extending the Hypertext Transfer Protocol (HTTP) require a feature to do partial resource modification. The existing HTTP PUT method only allows a complete replacement of a document. This proposal adds a new HTTP method, PATCH, to modify an existing HTTP resource.

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

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

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

关于资源路径,您可以有两个选项。

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

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

这是此 /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元素的子资源路径

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

这是此 /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) },
    (...)
]

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

  • 原子

在这种情况下,您可以利用 HTTP 状态。如果一切顺利,您将获得状态 200。如果不是,如果提供的数据不正确(例如活页夹 ID 无效)或其他情况,则另一种状态如 400

  • 非原子

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

  • 异步

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

在完成之前,我还想注意到 OData 规范解决了有关具有名为 navigation links 功能的实体之间关系的问题。也许你能看看这个 ;-)

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

希望对你有帮助, 蒂埃里

在我工作的一个项目中,我们通过实施我们称为 'Batch' 请求的东西解决了这个问题。我们定义了一个路径 /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 (Multi-Status),如下所示:

[  
   {
      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
   },
]

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

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 补丁.

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