REST: 用一个请求更新多个资源——这是标准的还是应该避免的?

一个简单的 REST API:

  • GET: item/{ id }-返回具有给定 id 的项的描述
  • PUT: item/{ id }-使用给定的 id 更新或创建项
  • DELETE: item/{ id }-删除具有给定 id 的项

现在我们讨论的 API 扩展:

  • GET: item? filter-返回与过滤器匹配的所有项 ID
  • 条目-按照 JSON 有效负载的描述更新或创建一组条目
  • [[ DELETE: item-删除 JSON 有效负载描述的项目列表]] <-不对

我现在对可以通过 PUT/DELETE 项/{ id }轻松访问的 DELETE 和 PUT 操作回收功能感兴趣。

问: 提供这样的 API 是否常见?

备选方案: 在单连接时代,多个请求发出多个请求是便宜的,而且由于一个更改要么成功要么失败,因此工作原子性更强。但是在 NOSQL 数据库时代,即使请求处理因内部服务器或其他原因而终止,列表中的一个更改可能已经发生。

[更新]

在考虑了 白宫网站标准Wikipedia: REST 示例之后,现在提出以下示例 API:

一个简单的 REST API:

  • GET: item/{ id }-返回具有给定 id 的项的描述
  • PUT: item/{ id }-使用给定的 id 更新或创建项
  • DELETE: item/{ id }-删除具有给定 id 的项

顶级资源 API:

  • GET: item? filter-返回与过滤器匹配的所有项 ID
  • 项目——按照 JSON 有效负载的描述更新或创建一组项目

不支持或禁止在/项上放置和删除。

使用 POST 似乎可以在一个封闭的资源中创建新项,而不是替换而是附加。

HTTP 语义 POST 阅读:

通过追加操作扩展数据库

如果 PUT 方法需要替换完整的集合以返回 HTTP 语义引用的等价表示:

一个给定表示的成功 PUT 将表明,在同一目标资源上的后续 GET 将导致在200(OK)响应中返回一个等价的表示。

[ UPDATE2]

对于多个对象的更新方面,似乎更加一致的一个替代方法是 PATCH 方法。PUT 和 PATCH 之间的区别在 RFC 5789草案中描述为:

PUT 和 PATCH 请求之间的区别反映在服务器处理封闭实体以修改由 Request-URI 标识的资源的方式上。在 PUT 请求中,封闭的实体被认为是存储在原始服务器上的资源的修改版本,客户端请求替换存储的版本。但是,对于 PATCH,封闭的实体包含一组指令,描述如何修改当前驻留在原始服务器上的资源以生成新版本。PATCH 方法影响由 Request-URI 标识的资源,它也可能对其他资源产生副作用; 例如,PATCH 应用程序可能创建新资源或修改现有资源。

因此,与 POST 相比,PATCH 可能也是一个更好的主意,因为 PATCH 允许 UPDATE,而 POST 只允许附加一些意味着添加的内容,而不允许修改。

所以 POST 在这里似乎是错误的,我们需要改变我们提出的 API:

一个简单的 REST API:

  • GET: item/{ id }-返回具有给定 id 的项的描述
  • PUT: item/{ id }-使用给定的 id 更新或创建项
  • DELETE: item/{ id }-删除具有给定 id 的项

顶级资源 API:

  • GET: item? filter-返回与过滤器匹配的所有项 ID
  • 项目——按照 JSON 有效负载的描述创建一个或多个项目
  • Item-按照 JSON 有效负载的描述创建或更新一个或多个项
84584 次浏览

As far as I understand the REST concept it covers an update of multiple resources with one request. Actually the trick here is to assume a container around those multiple resources and take it as one single resource. E.g. you could just assume that list of IDs identifies a resource that contains several other resources.

In those examples in Wikipedia they also talk about resources in Plural.

You could PATCH the collection, e.g.

PATCH /items
[ { id: 1, name: 'foo' }, { id: 2, name: 'bar' } ]

Technically PATCH would identify the record in the URL (ie PATCH /items/1 and not in the request body, but this seems like a good pragmatic solution.

To support deleting, creating, and updating in a single call, that's not really supported by standard REST conventions. One possibility is a special "batch" service that lets you assemble calls together:

POST /batch
[
{ method: 'POST', path: '/items', body: { title: 'foo' } },
{ method: 'DELETE', path: '/items/bar' }
]

which returns a response with status codes for each embedded requests:

[ 200, 403 ]

Not really standard, but I've done it and it works.

Updating Multiple Resources With One Request - Is it standard or to be avoided?

Well, sometimes you simply need to perform atomic batch operations or other resource-related operations that just do not fit the typical scheme of a simple REST API, but if you need it, you cannot avoid it.

Is it standard?

There is no universally accepted REST API standard so this question is hard to answer. But by looking at some commonly quoted api design guidelines, such as jsonapi.org, restfulapi.net, microsoft api design guide or IBM's REST API Conventions, which all do not mention batch operations, you can infer that such operations are not commonly understood as being a standard feature of REST APIs.

That said, an exception is the google api design guide which mentions the creation of "custom" methods that can be associated via a resource by using a colon, e.g. https://service.name/v1/some/resource/name:customVerb, it also explicitly mentions batch operations as use case:

A custom method can be associated with a resource, a collection, or a service. It may take an arbitrary request and return an arbitrary response, and also supports streaming request and response. [...] Custom methods should use HTTP POST verb since it has the most flexible semantics [...] For performance critical methods, it may be useful to provide custom batch methods to reduce per-request overhead.

So in the example case you provided you do the following according to google's api guide:

POST /api/items:batchUpdate

Also, some public APIs decided to offer a central /batch endpoint, e.g. google's gmail API.

Moreover, as mentioned at restfulapi.net, there is also the concept of a resource "store", in which you store and retrieve whole lists of items at once via PUT – however, this concept does not count for server-managed resource collections:

A store is a client-managed resource repository. A store resource lets an API client put resources in, get them back out, and decide when to delete them. A store never generates new URIs. Instead, each stored resource has a URI that was chosen by a client when it was initially put into the store.


Having answered your original questions, here is another approach to your problem that has not been mentioned yet. Please note that this approach is a bit unconventional and does not look as pretty as the typical REST API endpoint naming scheme. I am personally not following this approach but I still felt it should be given a thought :)

The idea is that you could make a distinction between CRUD operations on a resource and other resource-related operations (e.g. batch operations) via your endpoint path naming scheme.

For example consider a RESTful API that allows you to perform CRUD operations on a "company"-resource and you also want to perform some "company"-related operations that do not fit in the resource-oriented CRUD-scheme typically associated with restful api's – such as the batch operations you mentioned.

Now instead of exposing your resources directly below /api/companies (e.g. /api/companies/22) you could distinguish between:

  • /api/companies/items – i.e. a collection of company resources
  • /api/companies/ops – i.e. operations related to company resources

For items the usual RESTful api http-methods and resource-url naming-schemes apply (e.g. as discussed here or here)

POST    /api/companies/items
GET     /api/companies/items
GET     /api/companies/items/{id}
DELETE  /api/companies/items/{id}
PUT     /api/companies/items/{id}

Now for company-related operations you could use the /api/companies/ops/ route-prefix and call operations via POST.

POST    /api/companies/ops/batch-update
POST    /api/companies/ops/batch-delete
POST    /api/companies/ops/garbage-collect-old-companies
POST    /api/companies/ops/increase-some-timestamps-just-for-fun
POST    /api/companies/ops/perform-some-other-action-on-companies-collection

Since POST requests do not have to result in the creation of a resource, POST is the right method to use here:

The action performed by the POST method might not result in a resource that can be identified by a URI. https://www.rfc-editor.org/rfc/rfc2616#section-9.5