使用REST删除多个记录

185
什么是以REST风格删除多个条目的方法?
我的用例是,我有一个Backbone Collection,在其中需要能够一次性删除多个项目。选项似乎是:
1.为每个记录发送DELETE请求(如果可能有几十个项目,这似乎是一个坏主意); 2.在URL中连接要删除的ID(即,“/records/1;2;3”)并发送DELETE请求; 3.以非REST方式发送包含标记为删除的ID的自定义JSON对象。
所有选项都不理想。
这似乎是REST约定的一个灰色地带。

3
可能是Restful way for deleting a bunch of items的副本。 - Luka Žitnik
6个回答

156
  1. 这是一种可行的RESTful选择,但显然有你所描述的限制。
  2. 不要这样做。中介可能会将其解释为“删除/records/1;2;3处的(单个)资源”,因此对此的2xx响应可能会使他们清除缓存中的/records/1;2;3;而不是清除/records/1/records/2/records/3;代理一个针对/records/1;2;3的410响应,或者其他你认为没有意义的操作。
  3. 这是最好的选择,也可以以RESTful方式完成。如果您正在创建API并希望允许对资源进行批量更改,则可以使用REST来实现,但如何实现对许多人来说并不立即明显。一种方法是创建“更改请求”资源(例如通过将records=[1,2,3]作为主体POST到/delete-requests),并轮询已创建的资源(由响应的Location标头指定)以查找您的请求是否被接受、拒绝、正在进行或已完成。这对于长时间运行的操作非常有用。另一种方法是向列表资源发送PATCH请求/records,其中包含要对这些资源执行的资源和操作列表(以您希望支持的任何格式)。这对于快速操作非常有用,其中请求的响应代码可以指示操作的结果。

在不违反REST限制的前提下,一切均可实现,通常的答案是将“问题”转化为资源,并给出其URL。
因此,批量操作,例如在此处删除或将多个项目POST到列表中,或对大量资源进行相同的编辑,都可以通过创建“批量操作”列表并将新操作POST到其中来处理。

不要忘记,REST并不是解决任何问题的唯一方法。“REST”只是一种架构风格,您不必严格遵循它(但如果不遵循,您会失去互联网的某些好处)。建议查看HTTP API架构列表,选择适合您的架构。只需了解如果选择其他架构可能损失哪些东西,并根据您的用例做出明智的决策。
有一些错误的答案在“处理REST Web服务中的批操作的模式?”问题上得到了太多的赞同,但也应该被阅读。

3
你不必担心自己的服务器,而是要关注中间商、CDN、缓存代理等。因为互联网是一个分层系统,这也是它成功的原因之一。Roy 确定了系统成功所需的方面,并将它们命名为 REST。如果你发出一个 DELETE 请求,无论请求者和服务器之间存在什么,它们都会认为正在删除指定 URL 上的单个资源。对于这些设备来说,查询字符串是 URL 的不透明部分,因此你如何指定你的 API 并不重要,它们无法知道这些信息,因此不能有所不同。 - Nicholas Shanks
8
由于URI长度限制,如果您需要删除大量资源,则/records/1;2;3将无法正常工作。 - dukethrash
3
请注意,如果考虑使用DELETE方法并且有一个定义需要清除的资源的body,一些中间代理可能无法转发该body。另外,一些HTTP客户端不能在DELETE请求中添加body。详见https://dev59.com/UHVC5IYBdhLWcg3wbQfA - Luke Puplett
11
我会简述,使用DELETE请求时不允许传递请求主体。请勿这样做,否则我会吃掉你的孩子。咀嚼咀嚼咀嚼。 - Nicholas Shanks
3
#3 的论点问题在于,它带来的惩罚与反对 #2 的反驳论点相同。创建待删除的资源并非上游代理所知道如何处理的内容,这也是针对 #2 方法提出的反驳论点。 - LB2
显示剩余15条评论

25
如果 GET /records?filteringCriteria 返回符合条件的所有记录数组,则DELETE /records?filteringCriteria 可以删除所有这样的记录。
在这种情况下,你的问题的答案将是 DELETE /records?id=1&id=2&id=3

2
我也得出了这个结论:只需翻转动词以达到您想要做的效果。我不明白适用于GET的内容为何不能适用于DELETE。 - Luke Puplett
15
GET /records?id=1&id=2&id=3并不意味着“获取ID为1、2和3的三个记录”,而是意味着“获取URL路径为/records?id=1&id=2&id=3的单个资源”,该资源可能是一张萝卜图片,一个包含中文数字“42”的纯文本文档,或者可能不存在。 - Nicholas Shanks
考虑以下情况:发送两个连续的请求/records?id=1/records?id=2,并由某个中介(例如您的浏览器或ISP)缓存它们的响应。如果互联网知道您的应用程序对此的含义,那么根据这种理解,请求/records?id=1&id=2可以通过简单地合并(以某种方式)已经拥有的两个结果的缓存来返回,而不必询问原始服务器。但是这是不可能的。/records?id=1&id=2可能无效(每个请求仅允许一个ID),或者可能返回完全不同的内容(比如芜菁)。 - Nicholas Shanks
4
我非常不同意。如果结果被缓存了,那是服务器的问题。如果你谈论 API 的设计,希望你是正在编写服务器代码的人。无论你在查询字符串中使用 id[]=1&id[]=2 还是 id=1&id=2 来表示值的数组,该查询字符串仅表示这一点。我认为将查询字符串表示为过滤器是极其普遍且良好的实践。此外,如果允许删除和更新,请勿缓存“GET”请求。 如果这样做,客户端将保留陈旧的状态。 - Joseph Nields
我认为这个问题的主要问题在于,任何非幂等请求(特别是删除和更新)在同时运行多个记录的查询时应该仔细处理;主要是由于未预料到的边缘情况和错误。 - Abhishek Sharma
显示剩余5条评论

23

我认为Mozilla Storage Service SyncStorage API v1.5是使用REST删除多个记录的好方法。

删除整个集合。

DELETE https://<endpoint-url>/storage/<collection>

使用单个请求从集合中删除多个 BSO。

DELETE https://<endpoint-url>/storage/<collection>?ids=<ids>

ids: 删除指定逗号分隔的id列表中的BSO。最多可以提供100个id。

删除给定位置的BSO。

DELETE https://<endpoint-url>/storage/<collection>/<id>

http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions


2
似乎是一个不错的解决方案。我猜如果Mozilla认为它是正确的,那么它一定是正确的?唯一的问题是错误处理。假设他们传递了?ids=1,2,3,而id 3不存在,那么你是否删除1和2,然后响应200,因为请求者想要删除3,但它不存在,所以无关紧要?或者如果他们被授权删除1但不能删除2...你是否什么都不删除并响应错误,还是删除您可以删除的内容并留下其他内容... - tempcke
2
通常我会返回成功的响应,因为无论如何最终状态都是相同的。这样做可以简化客户端逻辑,因为它们不再需要处理错误状态。至于授权情况,我会直接失败整个请求...但实际上这取决于您的用例。 - Nathan Phetteplace
我同意Nathan的观点。 - Nicholas Shanks

8
这似乎是REST约定的一个灰色地带。
是的,到目前为止,我只看过一份REST API设计指南提到了批量操作(例如批量删除):谷歌API设计指南。
该指南提到了创建“自定义”方法,并可以通过使用冒号将其关联到资源,例如 https://service.name/v1/some/resource/name:customVerb,它还明确提到了批处理操作作为用例:
自定义方法可以与资源、集合或服务关联。它可能采用任意请求并返回任意响应,还支持流请求和响应。...自定义方法应该使用HTTP POST动词,因为它具有最灵活的语义...对于性能关键方法,提供自定义批处理方法以减少每个请求的开销可能很有用。
因此,根据谷歌的API指南,您可以执行以下操作:
POST /api/path/to/your/collection:batchDelete

删除集合资源中的一些项目。


通过 JSON 格式化的数组传递项目列表是可行的解决方案吗? - Daniele
是的,您可以通过POST负载发送JSON数组,其中包含ID。 - Felix K.
有趣的是,Google API指南在自定义方法章节中说:“如果用于自定义方法的HTTP动词不接受HTTP请求正文(GET、DELETE),则此类方法的HTTP配置不能使用body子句。”但是,“GET accounts.locations.batchGet”API是带有正文的GET方法。这很奇怪。https://developers.google.com/my-business/reference/rest/v4/accounts.locations/batchGet - 鄭元傑
@鄭元傑 同意,乍一看有些奇怪,但仔細看,它實際上是使用了 POST http 方法,只是自定義方法被命名為 batchGet。我想谷歌這樣做是為了 (a) 遵循他們所有自定義方法都需要是 POST 的規則(請參閱我的答案),以及 (b) 讓人們更容易在主體中放置“過濾器”,因此您不必像查詢字符串那樣對過濾器進行轉義或編碼。當然,缺點是這不再是可緩存的... - Felix K.
https://service.name/v1/some/resource/name:customVerb 根据定义不符合RESTful。 - deamon
1
@deamon - 为什么不呢?在这种情况下,自定义操作_IS_资源。而且你正在对它进行POST操作,正如Google API指南作者所指出的那样,这具有最灵活的语义。虽然ReST API通常看起来像薄的CRUD包装器,但这并不是必需的。 - Dave

2
我已经允许整个集合的全面替换,例如PUT ~/people/123/shoes,其中主体是整个集合表示。
这适用于客户端想要查看项目并修剪一些项目并添加其他项目然后更新服务器的小子项集合。他们可以PUT一个空集合来删除所有内容。
这意味着GET ~/people/123/shoes/9仍将保留在缓存中,即使PUT删除了它,但这只是一个缓存问题,如果其他人删除了鞋子,那么这将成为一个问题。
我的数据/系统API始终使用ETags而不是过期时间,因此服务器在每个请求上都会被命中,并且我需要正确的版本/并发标头来改变数据。对于只读和视图/报告对齐的API,我确实使用到期时间以减少对原点的命中,例如排行榜可以好10分钟。
对于更大的集合,如~/people,我倾向于不需要多个删除,用例不自然地出现,因此单个DELETE很好用。
未来,根据构建REST API和遇到相同问题和要求的经验,例如审计,我倾向于仅使用GET和POST动词,并围绕事件进行设计,例如POST地址更改事件,尽管我认为这将带来自己的一套问题:)
我还允许前端开发人员构建自己的API,以消耗更严格的后端API,因为他们通常有实际的、有效的客户端原因,不喜欢严格的“Fielding狂热者”REST API设计,以及生产力和缓存层理由。

我非常喜欢这个答案,直到读到最后一句话 :) 我从未见过应用严格的REST会产生净负面影响的用例。当然,在两端编写更多的代码可能会让人感到困扰,但最终你会得到一个更安全、更干净、更松散耦合的系统。 - Nicholas Shanks
哈哈,这实际上已经成为一种模式了!前端后端分离被称为 ThoughtWorks 技术雷达上的趋势。它还允许编写更多应用逻辑,而在 JavaScript 中可能会很繁琐,并且显然可以在没有客户端的情况下进行更新,例如 iOS 应用程序的更新。 - Luke Puplett
从谷歌的前四个搜索结果来看,似乎只有在客户端受到您控制的情况下,这种BFF技术才能发挥作用。客户端开发人员开发他们想要的API,将调用映射到真正的后端微服务API。在这个图表中:http://samnewman.io/patterns/architectural/bff/#bff 我会将“Perimeter”线放在BFF框下面——每个框只是客户端的一部分。它甚至可以存在于不包含微服务的数据中心之外。我也不明白REST如何不适用于两个接口(客户端/BFF和BFF/微服务)。 - Nicholas Shanks
1
是的,这是一个很好的观点。通常情况下,当您有一个构建微服务的团队和一个制作Angular应用程序的团队时,例如,那个开发团队更多的是前端类型,他们不喜欢与一堆小纯洁服务打交道。虽然我看不出为什么不能使用相同的模式来将微服务和聚合抽象成更可用的外观,以便于您的客户,这样微服务可以更改而不影响外观。 - Luke Puplett
1
API端点应该模拟领域和业务的需求。编写代码来解决这些问题,避免过度工程化以符合严格而又常常不灵活的规范。REST只是一些指南而已。 - Victorio Berra

2
您可以提交已删除的资源 :). URL 将为 POST /deleted-records 并且请求体将为 {"ids": [1, 2, 3]}


是的,HTTP DELETE请求允许包含实体主体,但通常不包括请求主体。在某些情况下,您可能需要在DELETE请求中发送数据,例如在RESTful API中使用。但是,请注意,这不是标准做法,并且可能会导致一些安全问题。 - Erik Philips
1
你想表达什么? - Lam Duc Trung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接