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

时间:2015-02-19 00:33:45

标签: rest api

我们假设有两个资源BinderDoc,其关联关系意味着DocBinder独立存在。 Doc可能属于或不属于BinderBinder可能为空。

如果我想设计一个REST API,允许用户发送Doc s 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
  • 处理批量请求,如果有几个项目引发错误但另一个项目通过,该怎么办?必须返回什么响应代码?批量操作应该是原子的吗?

4 个答案:

答案 0 :(得分:44)

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

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

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

  • 使用PATCH方法也很合适,因为相应的请求对应于部分更新。根据RFC5789(http://tools.ietf.org/html/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。如果没有,则另一个状态如400,如果提供的数据不正确(例如,活页夹ID无效)或其他。

  • 非原子

在这种情况下,将返回状态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/

希望它可以帮到你, 亨利

答案 1 :(得分:30)

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

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

您可以使用200.您也可以使用207 - Multi Status

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

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

答案 2 :(得分:15)

PUT ing

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

e.g:

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

PATCH 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博客条目。

答案 3 :(得分:7)

在我工作的一个项目中,我们通过实施一些我们称之为“批处理”的方式解决了这个问题。要求。我们定义了一个路径/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和谷歌有类似的实施:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests

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

  1. 替换为您发送的文件(即请求中的遗失属性将被删除并已存在已覆盖)?
  2. 与您发送的文档合并(即请求中的遗失属性不会被删除,现有属性将被覆盖)?
  3. 如果你想要替代1中的行为,你应该使用POST,如果你想要替代2中的行为,你应该使用PUT。

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

    正如人们已经建议你也可以选择PATCH,但我更喜欢保持API的简单,如果不需要,不要使用额外的动词。