[英]RESTful undelete
It is a fairly common requirement to support undeletes or delayed/batched deletions for data services. 支持数据服务的取消删除或延迟/批量删除是一个相当普遍的要求。 What I'm wondering is how to implement this in a RESTful way. 我想知道的是如何以RESTful方式实现它。 I'm torn between a few different options (none of which seems terribly attractive to me). 我在几个不同的选择之间徘徊(其中没有一个对我来说似乎非常有吸引力)。 Common across these different options, I believe, is the need for an API which returns all resource marked as deleted for a particular resource type. 我认为,这些不同选项的共同点是需要一个API,它返回标记为特定资源类型的已删除的所有资源。
Here are some options I've thought about and some of their pros/cons: 以下是我考虑过的一些选项以及它们的一些优点/缺点:
Options to mark resource as deleted: 将资源标记为已删除的选项:
Options when GET-ing resource marked for deletion: GET-ing资源标记为删除时的选项:
Options for responses which include this deleted resource: 包含此已删除资源的响应选项:
Options when updating a resource marked for deletion: 更新标记为删除的资源时的选项:
Options to undelete resource marked for deletion: 取消删除标记为删除的资源的选项:
This is by no means an exhaustive list. 这绝不是一份详尽的清单。 I just wanted to enumerate some of the options that have been bouncing around in my head. 我只是想列举一些在我脑海中蹦蹦跳跳的选项。
I know the answer to how to do this is, as usual, "it depends". 我知道如何做到这一点的答案就像往常一样,“它取决于”。 What I'm curious about is what qualifications/requirements would you use to make your decision? 我很好奇的是你会用什么资格/要求做出决定? How have you seen this implemented or implemented it yourself? 您是如何看待自己实施或实施的?
Going by the book: RFC 2616-9.7 : 通过这本书: RFC 2616-9.7 :
The DELETE method requests that the origin server delete the resource
identified by the Request-URI. This method MAY be overridden by human
intervention (or other means) on the origin server. The client cannot
be guaranteed that the operation has been carried out, even if the
status code returned from the origin server indicates that the action
has been completed successfully. However, the server SHOULD NOT
indicate success unless, at the time the response is given, if it intends
to delete the resource or move it to an inaccessible location.
When you DELETE a resource the server should mark the resource for deletion on it's side. 删除资源时,服务器应标记要删除的资源。 It doesn't really have to delete the resource, it just can't give any guarantee that the operation has been carried out. 它实际上不必删除资源,它只是不能保证已经执行了操作。 Even so, the server shouldn't say it's been deleted when it hasn't. 即便如此,服务器也不应该说它已经被删除了。
A successful response SHOULD be 200 (OK) if the response includes an entity
describing the status, 202 (Accepted) if the action has not yet been enacted,
or 204 (No Content) if the action has been enacted but the response does not
include an entity.
If the operation is delayed, send a 202 and an entity body describing the result of the action. 如果操作被延迟,则发送202和描述操作结果的实体主体。 (Think of a poll-able "task" representing the server's deferred deletion of the resource; It could theoretically leave it forever in that state.) All it has to do is prevent the client from retrieving it again in it's original form. (想想一个表示服务器延迟删除资源的可轮询的“任务”;理论上它可以永远保留在该状态。) 它所要做的只是阻止客户端以原始形式再次检索它。 Use a 410 for the response code, and when the "task" finishes or the server otherwise deletes the resource, return a 404. 使用410作为响应代码,当“任务”完成或服务器以其他方式删除资源时,返回404。
However, if a DELETE's semantics don't make sense for the resource in question, perhaps it's not a deletion you're looking for, but an addition state transition that alters the resource state but keeps it accessible? 但是,如果DELETE的语义对于有问题的资源没有意义,可能它不是您正在寻找的删除,而是一个添加状态转换,它改变资源状态但保持可访问性? In that case, use a PUT/PATCH to update the resource and be done. 在这种情况下,使用PUT / PATCH更新资源并完成。
I think the most RESTful way to solve this is to use HTTP PUT to mark the resource for deletion (and undelete) and then use HTTP DELETE to permanently delete the resource. 我认为解决此问题的最RESTful方法是使用HTTP PUT标记资源以进行删除(并取消删除),然后使用HTTP DELETE永久删除资源。 To get a list of resources marked for deletion I would use a parameter in the HTTP GET request eg. 要获取标记为删除的资源列表,我将使用HTTP GET请求中的参数,例如。 ?state=markedForDeletion
. ?state=markedForDeletion
。 If you requests a resource marked for deletion without the parameter, I think you should return a "404 Not Found" status. 如果您在没有参数的情况下请求标记为删除的资源,我认为您应该返回“404 Not Found”状态。
The Short Version 简短版
You cannot RESTfully undelete a resource using any method on it's original URI - it's illogical, because any operation attempted on a resource that has been deleted should return either a 404 or a 410. While this is not explicitly stated in the spec, it's strongly implied in the definition of the DELETE method 1 (emphasis added): 您无法使用原始URI上的任何方法RESTful取消删除资源 - 这是不合逻辑的,因为在已删除的资源上尝试的任何操作都应返回404或410.虽然规范中未明确说明,但它强烈暗示在DELETE方法1的定义中(强调添加):
In effect, this method is similar to the rm command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted. 实际上,此方法类似于UNIX中的rm命令:它表示对源服务器的URI映射执行删除操作,而不是期望删除先前关联的信息。
In other words, when you've DELETEd a resource, the server no longer maps that URI to that data. 换句话说,当您删除资源时,服务器不再将该URI映射到该数据。 So you can't PUT or POST to it to make an update like "mark this as undeleted" etc. (Remember that a resource is defined as a mapping between a URI and some underlying data). 因此,您无法对其进行PUT或POST以进行更新,例如“将此标记为未删除”等。(请记住,资源定义为URI与某些基础数据之间的映射)。
Some Solutions 一些解决方案
Since it's explicitly stated that the underlying data is not necessarily deleted, it doesn't preclude the server making a new URI mapping as part of the DELETE implementation, thereby effectively making a backup copy that can be restored later. 由于明确声明基础数据不一定被删除,因此不排除服务器在DELETE实现中创建新的 URI映射,从而有效地制作可以在以后恢复的备份副本。
You could have a "/deleted/" collection that contains all the deleted items - but how would you actually perform the undelete? 你可以有一个包含所有已删除项目的“/ deleted /”集合 - 但是你如何实际执行取消删除? Perhaps simplest RESTful way is to have the client retrieve the item with GET, and then POST it to the desired URL. 也许最简单的RESTful方法是让客户端使用GET检索项目,然后将其POST到所需的URL。
What if you need to be able to restore the deleted item to it's original location? 如果您需要将已删除的项目还原到其原始位置,该怎么办? If you're using a media type that supports it, you could include the original URI in the response to a GET from the /deleted/ collection. 如果您使用的是支持它的媒体类型,则可以在/ deleted / collection中对GET的响应中包含原始URI。 The client could then use it to POST. 然后客户端可以使用它进行POST。 Such a response might look like this in JSON: 这样的响应可能在JSON中看起来像这样:
{
"original-url":"/some/place/this/was/deleted/from",
"body":<base64 encoded body>
}
The client could then POST that body to that URI to perform an undelete. 然后,客户端可以将该主体POST到该URI以执行取消删除。
Alternatively, if your resource definition allows the concept of moving (by updating a "location" property or something like that) then you can do a partial update and avoid the round trip of the entire object. 或者,如果您的资源定义允许移动的概念(通过更新“位置”属性或类似的东西),那么您可以进行部分更新并避免整个对象的往返。 Or, do what most people do and implement an RPC-like operation to tell the server to move the resource! 或者,做大多数人做的事情并实现类似RPC的操作来告诉服务器移动资源! UnRESTful, yes but it will probably work fine in most situations. UnRESTful,是的,但在大多数情况下它可能会正常工作。
How You Decide These Things 你如何决定这些事情
Regarding the question of how you decide these things: you have to consider what delete means in the context of your application, and why you want it. 关于如何决定这些事情的问题:您必须考虑删除在您的应用程序的上下文中的含义,以及您为什么需要它。 In a lot of applications, nothing ever gets deleted, and "delete" really just means "exclude this item from all further queries/listings etc. unless I explicitly undelete it". 在很多应用程序中,没有任何内容被删除,“删除”实际上只是意味着“从所有进一步的查询/列表等中排除此项目,除非我明确取消删除它”。 So, it's really just a piece of metadata, or a move operation. 所以,它实际上只是一个元数据或移动操作。 In that case, why bother with HTTP DELETE? 在这种情况下,为什么要烦扰HTTP DELETE? One reason might be if you want a 2-tiered delete - a soft or temporary version that's undoable, and a hard/permanent version that's, well...not. 一个原因可能是如果你想要一个2层删除 - 一个可撤销的软版本或临时版本,以及一个硬件/永久版本,那么......不是。
Absent any specific application context, I'd be inclined to implement them like this: 没有任何特定的应用程序上下文,我倾向于像这样实现它们:
I don't want to see this resource any longer, for my convenience : POST a partial update to mark the resource as "temporarily deleted" 为方便起见,我不想再看到这个资源 : POST部分更新将资源标记为“暂时删除”
I don't want anyone to be able to reach this resource any longer because it's embarrassing/incriminating/costs me money/etc : HTTP DELETE 我不希望任何人能够再次访问此资源,因为它令人尴尬/有罪/花了我钱/等等 : HTTP DELETE
The next question to consider is: should the permanent delete only unmap the URI permanently, so that no one can link to it any longer, or is it necessary to purge the underlying data too? 下一个要考虑的问题是:永久删除是否应该永久取消映射URI,以便任何人都不能再链接到它,或者是否有必要清除基础数据? Obviously if you keep the data, then an administrator could restore even a "permanently" deleted resource (not through any RESTful interface however). 显然,如果您保留数据,那么管理员甚至可以恢复“永久”删除的资源(但不是通过任何RESTful接口)。 The downside of this is that if the owner of the data really wants it purged, then an admin has to do that outside the REST interface. 这样做的缺点是,如果数据的所有者确实希望它被清除,那么管理员必须在REST接口之外执行此操作。
"Deleted" (trashed) items also may be considered as a resource, right? “已删除”(已删除)项目也可能被视为资源,对吧? Then we can access this resource in one of these ways (eg for a deleted a user): 然后我们可以通过以下方式之一访问此资源(例如,对于已删除的用户):
PATCH deleted_users/{id}
PATCH trash/users/{id}
PATCH deleted/users/{id}
or some people may think this is more restful way: 或者有些人可能认为这是更安静的方式:
PATCH deleted/{id}?type=users
and in payload goes something like this: 并在有效载荷中执行以下操作:
{ deleted_at: null }
I'm also running in this problem and I've been looking on the Internet for what feels like the best solution. 我也遇到了这个问题,我一直在互联网上寻找最好的解决方案。 Since none of the main answers I can find seem correct to me, here is my own research results. 既然我找不到任何主要答案对我来说都是正确的,这是我自己的研究结果。
Others are right that the DELETE
is the way to go. 其他人是对的, DELETE
是要走的路。 You could include a flag to determine whether it's immediately a permanent DELETE
or a move to the trashcan (and probably only administrators can do an immediate permanent DELETE
.) 您可以包含一个标志,以确定它是立即永久DELETE
还是转移到垃圾桶(并且可能只有管理员可以立即执行永久DELETE
。)
DELETE /api/1/book/33
DELETE /api/1/book/33?permanent
The backend can then mark the book as deleted. 然后,后端可以将该书标记为已删除。 Assuming you have an SQL database, it could be something such as: 假设您有一个SQL数据库,它可能是这样的:
UPDATE books SET status = 'deleted' WHERE book_id = 33;
As mentioned by others, once the DELETE
is done, a GET
of the collection does not return that item. 正如其他人所提到的,一旦DELETE
完成,集合的GET
不会返回该项。 In terms of SQL, this means you must make sure not to return an item with a status of deleted
. 就SQL而言,这意味着您必须确保不返回状态为deleted
。
SELECT * FROM books WHERE status <> 'deleted';
Also, when you do a GET /api/1/book/33
, you must return a 404 or 410. One problem with 410 is that it means Gone Forever (at least that's my understanding of that error code,) so I would return 404 as long as the item exists but is marked as 'deleted'
and 410 once it was permanently removed. 此外,当您执行GET /api/1/book/33
,您必须返回404或410. 410的一个问题是它意味着永远消失(至少这是我对该错误代码的理解)所以我会返回404,只要该项目存在但被标记为'deleted'
并且一旦被永久删除就被标记为410。
Now to undelete, the correct way is to PATCH
. 现在要取消删除,正确的方法是PATCH
。 Contrary to a PUT
which is used to update an item, the PATCH
is expected to be an operation on an item. 与用于更新项目的PUT
相反, PATCH
应该是对项目的操作。 From what I can see, the operation is expected to be in the payload. 从我所看到的,操作预计将在有效载荷中。 For that to work, the resource needs to be accessible in some way. 为了实现这一点,需要以某种方式访问资源。 As someone else suggested, you can provide a trashcan
area where the book would appear once deleted. 正如其他人建议的那样,您可以提供一个trashcan
区域,一旦删除该图书就会出现。 Something like this would work to list books that were put in the trashcan: 这样的东西可以列出放入垃圾箱的书籍:
GET /api/1/trashcan/books
[{"path":"/api/1/trashcan/books/33"}]
So, the resulting list would now include book number 33, which you can then PATCH
with an operation such as: 因此,结果列表现在将包括第33号书籍,然后您可以使用以下操作进行PATCH
:
PATCH /api/1/trashcan/books/33
{
"operation": "undelete"
}
If you'd like to make the operation more versatile, you could use something such as: 如果您想使操作更加通用,您可以使用以下内容:
PATCH /api/1/trashcan/books/33
{
"operation": "move",
"new-path": "/api/1/books/33"
}
Then the "move" could be used for other changes of URL wherever possible in your interface. 然后,“移动”可以在您的界面中尽可能用于URL的其他更改。 (I am working on a CMS where the path to a page is in one table called tree
, and each page is in another table called page
and has an identifier. I can change the path of a page by moving it between paths in my tree
table! This is where a PATCH
is very useful.) (我正在使用CMS,其中页面的路径在一个名为tree
表中,每个页面都在另一个名为page
表中,并且有一个标识符。我可以通过在tree
路径之间移动来更改页面的路径table!这是PATCH
非常有用的地方。)
Unfortunately, the RFCs do not clearly define the PATCH
, only that it is to be used with an operation as shown above, opposed to a PUT
which accepts a payload representing a new version, possibly partial, of the targeted item: 不幸的是,RFC没有明确定义PATCH
,只是它与上面所示的操作一起使用,与PUT
相反, PUT
接受代表目标项目的新版本(可能是部分)的有效载荷:
PUT /api/1/books/33
{
"title": "New Title Here"
}
Whereas the corresponding PATCH
(if you were to support both) would be: 而相应的PATCH
(如果你同时支持两者)将是:
PATCH /api/1/books/33
{
"operation": "replace",
"field": "title",
"value": "New Title Here"
}
I think that supporting that many PATCH
operations would be crazy. 我认为支持那么多PATCH
操作会很疯狂。 But I think that a few good examples give a better idea of why PATCH
is the correct solution. 但我认为一些好的例子可以更好地了解为什么PATCH
是正确的解决方案。
You can think of it as: using patch is to change a virtual field or run a complex operation such as a move which would otherwise require a GET
, POST
, DELETE
(and that's assuming the DELETE
is immediate and you could get errors and end up with a partial move...) In a way, the PATCH
is similar to having any number of methods. 您可以将其视为:使用patch是更改虚拟字段或运行复杂操作,例如移动,否则需要GET
, POST
, DELETE
(并且假设DELETE
是立即的,您可能会收到错误并最终结束部分移动......)在某种程度上, PATCH
类似于拥有任意数量的方法。 An UNDELETE
or MOVE
method would work in a similar way, but the RFC clearly says there is a set of standardized methods and you should certainly stick to them and the PATCH
gives you plenty of room to not have to add your own methods. UNDELETE
或MOVE
方法可以以类似的方式工作,但RFC清楚地说有一套标准化的方法 ,你当然应该坚持它们,而PATCH
给你足够的空间来不必添加你自己的方法。 Although I did not see anything in the specs saying you should not add your own methods. 虽然我在规范中没有看到任何说明你不应该添加自己的方法。 If you do, though, make sure to clearly document them. 但是,如果您这样做,请务必清楚地记录它们。
We've forced the model creating a 我们强迫模型创建了一个
POST /modelname/:id/undelete POST / modelname /:id / undelete
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.