支持数据服务的取消删除或延迟/批量删除是一个相当普遍的要求 . 我想知道的是如何以RESTful方式实现它 . 我在几个不同的选项之间徘徊(其中没有一个对我来说似乎非常有吸引力) . 我认为,这些不同选项的共同点是需要一个API,它返回标记为特定资源类型的已删除的所有资源 .
以下是我考虑过的一些选项以及它们的一些优点/缺点:
将资源标记为已删除的选项:
-
使用HTTP DELETE将资源标记为已删除 .
-
使用HTTP PUT / POST更新已删除的标志 . 这感觉不对,因为它将基本上是删除的内容从HTTP DELETE方法映射到其他HTTP方法 .
GET-ing资源标记为删除时的选项:
-
返回标记为已删除的资源的HTTP状态404 . 干净透明,但我们如何区分真正删除的资源与刚删除的资源之间的区别 .
-
返回HTTP状态410.提供告诉区别的方法,但410技术上说它"is expected to be considered permanent. Clients with link editing capabilities SHOULD delete references to the Request-URI after user approval."这里的单词"expected"和"SHOULD"可能有足够的摆动空间 . 不确定客户端中410的支持/理解程度如何 .
-
返回HTTP状态200并包括指示资源被删除的标志字段 . 这似乎很奇怪,因为首先删除它的想法是因为你实际上想要它不出现 . 这推动了将已删除资源过滤到客户端的责任 .
包含此已删除资源的响应选项:
-
忽略已删除的资源 . 干净简单 . 但是,如果您真的想了解已删除的资源,该怎么办?
-
将它们与表示已删除的字段一起包括在内 . 这推动了将已删除资源过滤到客户端的责任 . 如果您只想浏览活动或已删除的资源,则会使分页变得棘手 .
更新标记为删除的资源时的选项:
-
使用HTTP状态404.资源是否正确?但是,如何区分标记为已删除的资源与实际删除的资源之间的区别 . 404响应中的HTTP正文可以在此消除歧义,但随后客户端将解析/解释您的正文以消除歧义 . 也许响应 Headers 可能有帮助吗?哪一个?自定义 Headers ?
-
使用HTTP状态409以及有关必须首先取消删除资源的消息 .
取消删除标记为删除的资源的选项:
-
使用HTTP PUT / POST进行资源的更新操作,并再次将其标记为活动状态 . 只有在自从PUT / POST到资源"Not found"(404)之后才能生效 .
-
使用HTTP PUT / POST进行资源的创建操作 . 这里的问题是哪些数据优先?在创建操作中发送的数据?或者正在取消删除的数据?将其从任何其他可能返回的查询中过滤掉 . 然后,如果资源标识符指向标记为已删除的资源,则将创建资源的HTTP PUT / POST视为取消删除 .
-
专用于取消删除标记为删除的资源的独立REST路径 .
这绝不是一份详尽的清单 . 我只是想列举一些在脑子里蹦蹦跳跳的选项 .
我知道如何做到这一点的答案就像往常一样,“它取决于” . 我很好奇的是你会用什么资格/要求做出决定?您是如何看待自己实施或实施的?
5 回答
读完这本书:RFC 2616-9.7:
删除资源时,服务器应将资源标记为删除 . 它实际上不必删除资源,它只是不能保证已经执行了操作 . 即便如此,服务器也不应该说它已经被删除了 .
如果操作被延迟,则发送202和描述操作结果的实体主体 . (想想一个可轮询的"task"表示服务器延迟删除资源;理论上它可以永远保留在该状态 . ) All it has to do is prevent the client from retrieving it again in it's original form. 使用410作为响应代码,当"task"完成或服务器以其他方式删除资源时,返回404 .
但是,如果DELETE的语义对于有问题的资源没有意义,可能它不是您正在寻找的删除,而是一个添加状态转换,它改变资源状态但保持可访问性?在这种情况下,使用PUT / PATCH更新资源并完成 .
我认为解决此问题的最RESTful方法是使用HTTP PUT标记资源以进行删除(和取消删除),然后使用HTTP DELETE永久删除资源 . 要获取标记为删除的资源列表,我将使用HTTP GET请求中的参数,例如 .
?state=markedForDeletion
. 如果您在没有参数的情况下请求标记为删除的资源,我认为您应该返回"404 Not Found"状态 .The Short Version
你不能使用任何方法重新取消删除资源's original URI - it'不合逻辑,因为在已删除的资源上尝试的任何操作都应该返回404或410.虽然规范中没有明确说明,但它在定义中强烈暗示DELETE方法1(强调添加):
换句话说,当您删除资源时,服务器不再将该URI映射到该数据 . 因此,您无法对其进行PUT或POST以进行更新,例如“将此标记为未删除”等 . (请记住,资源定义为URI与某些基础数据之间的映射) .
Some Solutions
因为它阻止服务器作为DELETE实现的一部分进行新的URI映射,从而有效地制作可以在以后恢复的备份副本 .
您可以拥有一个包含所有已删除项目的“/ deleted /”集合 - 但您将如何实际执行取消删除?也许最简单的RESTful方法是让客户端使用GET检索项目,然后将其POST到所需的URL .
如果您需要将已删除的项目还原到其原始位置,该怎么办?如果您使用的是支持它的媒体类型,则可以在/ deleted / collection中对GET的响应中包含原始URI . 然后客户端可以使用它进行POST . 这样的响应可能在JSON中看起来像这样:
然后,客户端可以将该主体POST到该URI以执行取消删除 .
或者,如果您的资源定义允许移动的概念(通过更新“位置”属性或类似的东西),那么您可以进行部分更新并避免整个对象的往返 . 或者,做大多数人做的事情并实现类似RPC的操作来告诉服务器移动资源! UnRESTful,是的,但在大多数情况下它可能会正常工作 .
How You Decide These Things
关于如何决定这些事情的问题:您必须考虑删除在您的应用程序上下文中的含义以及您为什么需要它 . 在很多应用程序中,没有任何东西被删除,“删除”实际上只是意味着“从所有进一步的查询/列表等中排除此项目,除非我明确取消删除它” . 所以,它实际上只是一个元数据或移动操作 . 在这种情况下,为什么要烦扰HTTP DELETE?一个原因可能是如果你想要一个2层删除 - 一个可以撤销的软版本或临时版本,以及一个硬件/永久版本,那么......不是 .
没有任何特定的应用程序上下文,我倾向于像这样实现它们:
为方便起见,我不想再看到这个资源: POST a partial update to mark the resource as "temporarily deleted"
我不会尴尬/犯罪/花我钱/等等: HTTP DELETE
下一个要考虑的问题是:永久删除是否应该永久取消映射URI,以便任何人都不能再链接到它,或者是否有必要清除基础数据?显然,如果您保留数据,那么管理员甚至可以恢复“永久”删除的资源(但不是通过任何RESTful接口) . 这样做的缺点是,如果数据的所有者确实希望它被清除,那么管理员必须在REST接口之外执行此操作 .
“已删除”(已删除)项目也可能被视为资源,对吧?然后我们可以通过以下方式之一访问此资源(例如,对于已删除的用户):
或者有些人可能认为这是更安静的方式:
并在有效载荷中是这样的:
我也遇到了这个问题,而且我一直在互联网上寻找最好的解决方案,因为我找不到任何主要答案对我来说都是正确的 .
其他人是正确的,
DELETE
是要走的路 . 您可以包含一个标志,以确定它是立即永久DELETE
还是转移到垃圾桶(并且可能只有管理员可以立即执行DELETE
. )然后,后端可以将该书标记为已删除 . 假设您有一个SQL数据库,它可能是这样的:
正如其他人所提到的,一旦
DELETE
完成,该集合的GET
不会返回该项目 . 就SQL而言,这意味着您必须确保不返回状态为deleted
的项目 .此外,当你执行
GET /api/1/book/33
时,你必须返回404或410. 410的一个问题是它意味着永远消失(至少这是我对该错误代码的理解)所以只要该项目存在我就会返回404但是永久删除后标记为'deleted'
和410 .现在要取消删除,正确的方法是
PATCH
. 与用于更新项目的PUT
相反,PATCH
应该是对项目的操作 . 从我所看到的,操作预计将在有效载荷中 . 为了实现这一点,需要以某种方式访问资源 . 和别人一样建议,您可以提供一个trashcan
区域,该区域将在删除后出现 . 像这样的东西可以列出放入垃圾桶的书籍:因此,结果列表现在将包括第33号书籍,然后您可以使用以下操作
PATCH
:如果您想使操作更通用,您可以使用如下内容:
然后,"move"可以在您的界面中尽可能用于URL的其他更改 . (我正在使用CMS,其中页面的路径位于一个名为
tree
的表中,每个页面位于另一个名为page
的表中,并且具有标识符 . 我可以通过在tree
中的路径之间移动来更改页面的路径table!这是PATCH
非常有用的地方 . )遗憾的是,RFC并没有明确定义
PATCH
,只是它将与上面所示的操作一起使用,而不是PUT
,它接受代表目标项目的新版本(可能是部分)的有效负载:而相应的
PATCH
(如果你同时支持两者)将是:我认为支持许多
PATCH
操作会很疯狂 . 但我认为一些好的例子可以更好地了解为什么PATCH
是正确的解决方案 .您可以将其视为:使用patch是更改虚拟字段或运行复杂操作,例如移动,否则需要
GET
,POST
,DELETE
(并且假设DELETE
是立即的,您可能会收到错误并最终结束部分移动......)在某种程度上,PATCH
类似于拥有任意数量的方法 .UNDELETE
或MOVE
方法可以以类似的方式工作,但RFC清楚地说有一组standardized methods并且你当然应该坚持它们并且PATCH
给你足够的空间来不必添加自己的方法 . 虽然我在规范中没有看到任何说明你不应该添加自己的方法 . 但是,如果您这样做,请务必清楚地记录它们 .