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

假设有两个具有关联关系的资源 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中的每个文档,

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

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

  • 使用什么 HTTP 方法?
  • 必须返回什么响应代码?
  • 这是否符合 REST 的要求?
  • URI 是什么样子的? /binders/docs
  • 处理批量请求时,如果一些项引发错误,而另一些项执行。必须返回什么响应代码?批量操作应该是原子操作吗?
137658 次浏览

PUT ing

创建或更新,并将单个文档与活页夹关联起来

例如:

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

PATCH ing

如果文档不存在,则创建文档并将其与活页夹相关联

例如:

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和威廉杜兰德的 拜托,别像个白痴一样修补博客条目。

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

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

您可以使用200。您也可以使用 207-多重身份

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

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

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

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

    In the case, it's not necessary to define your format to describe the update.

  • 使用 PATCH方法 也是合适的,因为相应的请求对应于部分更新:

    一些扩展超文本传输协议(HTTP)的应用程序需要一个特性来进行部分资源修改。现有的 HTTPPUT 方法只允许完全替换文档。该建议添加了一个新的 HTTP 方法 PATCH 来修改现有的 HTTP 资源。

    In the case, you have to define your format to describe the partial update.

I think that in this case, POST and PATCH are quite similar since you don't really need to describe the operation to do for each element. I would say that it depends on the format of the representation to send.

PUT的情况就不那么清楚了。实际上,在使用方法 PUT时,应该提供整个列表。事实上,请求中提供的表示将取代列表资源。

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

  • Using the resource path for doc list

在这种情况下,您需要在请求中提供的表示中显式地提供带有绑定器的文档链接。

下面是这个 /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/{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的状态。

  • 不是原子弹

In this case, a status 200 will be returned and it's up to the response representation to describe what was done and where errors eventually occur. ElasticSearch has an endpoint in its REST API for bulk update. This could give you some ideas at this level: 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/

希望能帮到你, 蒂埃里

In a project I worked at we solved this problem by implement something we called 'Batch' requests. We defined a path /batch where we accepted json in the following format:

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

The response have the status code 207 (Multi-Status) and looks like this:

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

You could also add support for headers in this structure. We implemented something that proved useful which was variables to use between requests in a batch, meaning we can use the response from one request as input to another.

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

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

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

如果您想要选项1的行为,您应该使用 职位 PUT,如果您想要选项2的行为,您应该使用 PATCH。

Http://restcookbook.com/http%20methods/put-vs-post/