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

nor*_*tpy 80 api rest

让我们假设有两个资源Binder,并Doc具有关联关系意味着DocBinder自己站.Doc可能属于也可能不属于BinderBinder可能为空.

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

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}
Run Code Online (Sandbox Code Playgroud)

对于每个文档docs,

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

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

  • 使用什么HTTP方法?
  • 必须返回什么响应代码?
  • 这甚至适合REST吗?
  • URI怎么样?/binders/docs
  • 处理批量请求,如果一些项目引发错误但另一个项目通过该怎么办.必须返回什么响应代码?批量操作应该是原子的吗?

Thi*_*ier 51

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

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

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

  • 使用PATCH方法也是合适的,因为相应的请求对应于部分更新.根据RFC5789(http://tools.ietf.org/html/rfc5789):

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

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

我认为在这种情况下,POST并且PATCH非常相似,因为您实际上不需要描述要为每个元素执行的操作.我会说它取决于要发送的表示的格式.

情况PUT有点不太清楚.实际上,在使用方法时PUT,您应该提供整个列表.事实上,请求中提供的表示将替换列表资源1.

您可以有两个有关资源路径的选项.

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

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

这是一个示例路线/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) },
    (...)
]
Run Code Online (Sandbox Code Playgroud)
  • 使用binder元素的子资源路径

此外,您还可以考虑利用子路径来描述文档和绑定器之间的链接.现在,在请求内容中没有指定有关doc和binder之间关联的提示.

这是一个示例路线/binder/{binderId}/docs.在这种情况下,使用方法发送文档列表,POST或者在创建文档后将PATCH文档附加到带有标识符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) },
    (...)
]
Run Code Online (Sandbox Code Playgroud)

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

  • 原子

在这种情况下,您可以利用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/.

希望它对你有帮助,蒂埃里


Dar*_*ler 33

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

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

你可以使用200.你也可以使用207 - 多状态

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

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


Mau*_*les 18

PUT ING

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

例如:

PUT /binders/1/docs HTTP/1.1
{
  "docNumber" : 1
}
Run Code Online (Sandbox Code Playgroud)

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 } }
] 
Run Code Online (Sandbox Code Playgroud)

我稍后会包含其他见解,但与此同时,如果您愿意,请查看RFC 5789,RFC 6902和William Durand的Please.不要像Idiot博客条目那样修补.

  • 有时客户端需要批量操作,并且不想关心资源是否存在.正如我在问题中所说,客户希望发送一堆`docs`并将它们与`binders`联系起来.如果不存在,客户端想要创建绑定器,如果存在则建立关联.在ONE SINGLE BULK请求中. (2认同)

Dav*_*erg 10

在我工作的一个项目中,我们通过实现我们称为"批处理"请求的东西解决了这个问题.我们定义了一个/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
      }
   },
]
Run Code Online (Sandbox Code Playgroud)

响应的状态代码为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
   },
]
Run Code Online (Sandbox Code Playgroud)

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

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,如果你想要替代2的行为,你应该使用PUT.

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

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

  • 喜欢这个概念验证以及谷歌和Facebook链接的答案.但不同意关于POST或PUT的结尾部分.在这个答案提到的2个案例中,第一个应该是PUT,第二个应该是PATCH. (5认同)
  • 因为这就是PATCH的发明.您可以阅读[此定义](https://tools.ietf.org/html/rfc5789#section-2)并查看PUT和PATCH如何匹配您的2个要点. (2认同)