REST:通过一个请求更新多个资源-是标准的还是要避免的?


75

一个简单的REST API:

  • GET:items / {id}-返回具有给定id的商品的描述
  • PUT:items / {id}-更新或创建具有给定id的项目
  • 删除:items / {id}-删除具有给定id的项目

现在有问题的API扩展:

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

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

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

备选方案:在单连接多个请求时代,发出多个请求很便宜,并且由于更改成功或失败而工作起来更加原子,但是在NOSQL数据库时代,即使请求处理终止,列表中的更改也可能已经发生。内部服务器或出于任何原因的任何原因。

[更新]

在考虑了白宫Web标准维基百科:REST示例之后,现在使用以下示例API:

一个简单的REST API:

  • GET:items / {id}-返回具有给定id的商品的描述
  • PUT:items / {id}-更新或创建具有给定id的项目
  • 删除:items / {id}-删除具有给定id的项目

顶级资源API:

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

不支持/禁止在/ items上执行PUT和DELETE。

使用POST似乎可以解决问题,因为它是一种在封闭资源中创建新项目而不是替换而追加的方法。

HTTP语义POST读取:

通过追加操作扩展数据库

PUT方法需要替换完整的集合以便返回HTTP语义PUT引用的等效表示形式:

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

[UPDATE2]

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

PUT和PATCH请求之间的差异体现在服务器处理封闭实体以修改由Request-URI标识的资源的方式。在PUT请求中,封闭的实体被视为原始服务器上存储的资源的修改版本,并且客户端正在请求替换存储的版本。但是,对于PATCH,封闭的实体包含一组指令,这些指令描述了应如何修改当前驻留在源服务器上的资源以产生新版本。PATCH方法影响由Request-URI标识的资源,并且可能对其他资源也有副作用。也就是说,可以通过应用PATCH来创建新资源或修改现有资源。

因此,与POST相比,PATCH可能也是一个更好的主意,因为PATCH允许进行UPDATE,而POST仅允许附加一些意味着添加而无需修改的机会。

因此POST在这里似乎是错误的,我们需要将建议的API更改为:

一个简单的REST API:

  • GET:items / {id}-返回具有给定id的商品的描述
  • PUT:items / {id}-更新或创建具有给定id的项目
  • 删除:items / {id}-删除具有给定id的项目

顶级资源API:

  • GET:item?filter-返回与过滤器匹配的所有项目ID
  • POST:项目-按照JSON有效负载的描述创建一个或多个项目
  • 修补程序:项目-按照JSON有效负载的描述创建或更新一个或多个项目

可能有帮助:github.com/WhiteHouse/api-standards#http-verbs。顺便说一句,DELETE请求没有定义的主体语义(stackoverflow.com/a/5928241/1225328)。
sp00m 2015年

Answers:


52

您可以修补集合,例如

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

从技术上讲,PATCH会在URL中标识记录(即PATCH,/items/1而不是请求正文中的记录),但这似乎是一个不错的实用解决方案。

为了支持在单个调用中进行删除,创建和更新,标准REST约定实际上并没有对此提供支持。一种可能性是特殊的“批处理”服务,它使您可以将调用组合在一起:

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

它为每个嵌入的请求返回带有状态代码的响应:

[ 200, 403 ]

这不是真正的标准,但是我已经做到了,并且可以正常工作。


批处理想法很有趣。您是如何实现的?是真正的调度还是内部处理?考虑到今天只需要一个连接,发出多个请求而不是单个请求会导致这种性能损失(延迟,带宽)吗?
Martin Kersten

最初,它是在内部处理,并且与各个查询分开处理,主要是出于性能方面的考虑。(您可以进行优化,例如执行单个SQL查询而不是N个查询。)但是由于一些令人困惑的不一致之处,最终对其进行了重构,以便在批处理命令和“真实”原子命令之间有效地共享相同的代码。尽管有必要,您可以通过一些缓存和优化来减轻性能,但这对性能的影响更大。
mahemoff

如果可以使用HTTP / 2,则该方案可能是不必要的,因为多个调用的性能开销可能很低。
mahemoff

我考虑过通过提供一种通用方法来提供单个批处理(代理/调度程序),该方法接受一组已定义的请求,只是将那些请求重新发布到同一服务器实例或数据中心内部,并收集结果。特别是在将事物分组在一起或提供更好的压缩,共享重复的参数和引用以及各种形式时,这可能会变得很方便。因此,基本上,它将作为原始请求被分发为原始海报。
马丁·克斯顿

1
同意。那将更像是任务计划程序或工作流处理器。但是,您仍然需要指定批处理的哪一部分可以并行处理,以及形成一个序列,可以通过添加抽象层(例如“任务”)轻松完成:[[A]:[request,request],“ B “:[...]]。对于某些类型的验收测试也将是一件好事,因为只需要编写JSON命令和JSON期望即可。对于REST API测试将是非常好的。我牢记这一点。
Martin Kersten

27

通过一个请求更新多个资源-是标准的还是要避免的?

好吧,有时候您只需要执行原子批处理操作或其他与资源相关的操作,这些操作不适合简单的REST API的典型方案,但是如果您需要它,则无法避免。

是标准的吗?

由于没有通用的REST API标准,因此很难回答这个问题。但是通过查看一些常用的api设计指南,例如jsonapi.orgrestfulapi.netmicrosoft api设计指南IBM的REST API约定,它们都没有提及批处理操作,您可以推断出这种操作通常不被理解为是REST API的标准功能。

也就是说,google api设计指南是一个例外,其中提到了创建“自定义”方法的过程,该方法可以通过使用冒号通过资源进行关联,例如https://service.name/v1/some/resource/name:customVerb,它还明确提到了批处理作为用例:

定制方法可以与资源,集合或服务相关联。它可以接受任意请求并返回任意响应,并且还支持流式请求和响应。自定义方法应使用HTTP POST动词,因为它具有最灵活的语义。对于性能至关重要的方法,提供自定义批处理方法以减少每个请求的开销可能很有用。

因此,在示例中,您根据Google的api指南执行了以下操作:

POST /api/items:batchUpdate

另外,一些公共API决定提供中央/batch端点,例如google的gmail API

此外,如提及在restfulapi.net,也有资源“存储”,在其中存储和检索项目的整个列表一次通过PUT的概念-但是,这个概念并不服务器管理资源集合数:

商店是客户端管理的资源存储库。存储资源使API客户端可以放入资源,将其撤回并决定何时删除它们。商店永远不会生成新的URI。取而代之的是,每个存储的资源都有一个URI,该URI是客户端在最初将其放入商店时选择的。


回答了您原来的问题后,这是尚未提及的另一种解决方法。请注意,这种方法有点不常规,看起来不像典型的REST API端点命名方案。我个人并没有遵循这种方法,但是我仍然认为应该考虑一下:)

这个想法是,您可以通过端点路径命名方案来区分资源上的CRUD操作和其他与资源相关的操作(例如批处理操作)。

例如,考虑一个RESTful API,该API允许您在“公司”资源上执行CRUD操作,并且还希望执行一些与“公司”相关的操作,这些操作通常不适合与静态api关联的面向资源的CRUD方案。 –例如您提到的批处理操作。

现在,您无需区分资源/api/companies(例如/api/companies/22),而是可以区分以下两者:

  • /api/companies/items –即公司资源的集合
  • /api/companies/ops –即与公司资源有关的运营

对于items通常的RESTful API,将应用http-methods和resource-url naming-schemes(例如,如此此处所讨论的)

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

现在,对于公司相关的操作,您可以/api/companies/ops/通过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

由于POST请求不必导致资源的创建,因此POST是在此处使用的正确方法:

POST方法执行的操作可能不会导致可以由URI标识的资源。 https://tools.ietf.org/html/rfc2616#section-9.5


2
从美学的角度来看,我发现Google冒号惯例有些不为人所知。当发明一个新的动词时,更常见的是在其后面加上一个斜杠并发布一些内容- POST /api/items/batch-update。我将完全承认存在一个弊端,那就是现在您有一个保留字,如果需要子关联,则不能将其用于子关联,但我会权衡取舍以使用更干净的URL 。
mahemoff '19

1
@mahemoff好点。您建议的是我上面提到的技术的一种快捷方式。在我的回答中,我将操作与资源分开,以(a)避免动词和资源之间的任何名称空间冲突,以及(b)遵守在REST url中“路径段”仅包含“相同”类型资源的想法。但是考虑到生成的ID无论如何通常都是UUID或整数,可以通过编程轻松地将它们与自定义“动词”(/api/users/3vs /api/users/batch-update)区分,因此名称空间不会重叠,因此您的方法也是合理的。
B12Toaster

There is no universally accepted REST API standard 这就是为什么它是低劣的标准。不支持原子批处理操作意味着此“标准”尚未准备好用于Prime-Time。我构建财务应用程序,并且最低要求是原子批处理操作(带有乐观并发检查)。我的客户想要REST,但是说实话,这是出于爱好。
Quarkly

2
@Quarkly REST更像是“概念”而不是“标准”。它仅在如何传输资源方面施加了很少的限制,并且没有详细说明应该如何设计REST API,因此将其留给开发人员如何处理。由您的团队/公司来定义可以操作REST API的规则。存在一些设计准则,这些准则是“好的”,而有些则是“不好的”,但是,说这是一个“糟糕的标准”并不能真正使REST作为一种概念/哲学,因此会产生误导。
B12Toaster

1

据我了解的REST概念,它包含一个请求即可更新多个资源。实际上,这里的技巧是假设围绕这些多个资源的容器并将其作为一个资源。例如,您可以假设ID列表标识包含其他多个资源的资源。

维基百科的那些例子中他们还讨论了Plural中的资源。


1
在Wikipedia示例中,他们指出PUT和POST的处理方式不同,并且应该宁愿使用POST来编写一个或多个新实体。
马丁·克斯顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.