66

支持数据服务的取消删除或延迟/批量删除是相当普遍的要求。我想知道的是如何以 RESTful 方式实现这一点。我在几个不同的选择之间左右为难(其中没有一个对我来说非常有吸引力)。我相信,这些不同选项的共同点是需要一个 API,它返回所有标记为已删除的特定资源类型的资源。

以下是我考虑过的一些选项以及它们的一些优点/缺点:

将资源标记为已删除的选项:

  • 使用 HTTP DELETE 将资源标记为已删除。
  • 使用 HTTP PUT/POST 更新已删除标志。这感觉不对,因为它将本质上是从 HTTP DELETE 方法中删除的内容映射到其他 HTTP 方法中。

获取标记为删除的资源时的选项:

  • 为标记为已删除的资源返回 HTTP 状态 404。干净透明,但是我们如何区分真正删除的资源与刚刚标记为已删除的资源之间的区别。
  • 返回 HTTP 状态 410。提供区分差异的方法,但 410 从技术上讲它“预计将被视为永久性的。具有链接编辑功能的客户端应该在用户批准后删除对 Request-URI 的引用。” 这里的“预期”和“应该”这两个词可能有足够的回旋余地。不确定客户端对 410 的支持/理解程度如何。
  • 返回 HTTP 状态 200 并包含指示资源已删除的标志字段。这看起来很奇怪,因为首先删除它的想法是因为您实际上希望它不出现。这将过滤已删除资源的责任推给了客户端。

包含此已删除资源的响应选项:

  • 省略标记为已删除的资源。干净简单。但是,如果您真的想了解已删除的资源怎么办。
  • 包括它们以及表明它们已被删除的字段。这将过滤已删除资源的责任推给了客户端。如果您只想对活动或已删除的资源进行分页,则分页会变得很棘手。

更新标记为删除的资源时的选项:

  • 使用 HTTP 状态 404。资源不见了对吗?但是,您如何区分标记为已删除的资源和实际已删除的资源。404 响应中的 HTTP 正文可以在这里消除歧义,但随后客户端需要解析/解释您的正文以消除歧义。也许响应标头在这里可能会有所帮助?哪一个?自定义标题?
  • 将 HTTP 状态 409 与有关如何首先取消删除资源的消息一起使用。

取消删除标记为删除的资源的选项:

  • 使用 HTTP PUT/POST 进行资源更新操作并再次将其标记为活动。这仅在您没有为资源的 GET 操作返回 HTTP 404 时才有效,因为它不会自 PUT/POST 到“未找到”(404) 的资源。
  • 使用 HTTP PUT/POST 进行资源的创建操作。这里的问题是哪些数据优先?在创建操作中发送的数据?还是正在取消删除的数据?将它从任何其他可能返回它的查询中过滤出来。然后,如果资源标识符指向标记为已删除的资源,则将创建资源的 HTTP PUT/POST 视为取消删除。
  • 单独的 REST 路径专用于取消删除标记为删除的资源。

这绝不是一份详尽的清单。我只是想列举一些一直在我脑海中蹦蹦跳跳的选项。

我知道如何做到这一点的答案是,像往常一样,“这取决于”。我很好奇的是你会用什么资格/要求来做出决定?你是如何看到这个实现或自己实现的?

4

6 回答 6

11

按书行事: 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.

当您删除资源时,服务器应在其一侧标记要删除的资源。它并不一定要删除资源,它只是不能保证操作已经执行。即便如此,服务器不应该说它没有被删除。

  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.

如果操作延迟,则发送 202 和描述操作结果的实体主体。(想想一个代表服务器延迟删除资源的可轮询“任务”;理论上它可以永远保持这种状态。)它所要做的就是阻止客户端再次以原始形式检索它。使用 410 作为响应代码,当“任务”完成或服务器以其他方式删除资源时,返回 404。

但是,如果 DELETE 的语义对所讨论的资源没有意义,也许它不是您正在寻找的删除,而是改变资源状态但保持其可访问性的附加状态转换?在这种情况下,使用 PUT/PATCH 更新资源并完成。

于 2011-12-24T02:10:55.850 回答
6

短版

您不能使用原始 URI 上的任何方法对资源进行 RESTful 取消删除 - 这是不合逻辑的,因为尝试对已删除资源的任何操作都应返回 404 或 410。虽然规范中没有明确说明,但强烈暗示在 DELETE 方法1的定义中(强调添加):

实际上,此方法类似于 UNIX 中的 rm 命令:它表示对源服务器的 URI 映射的删除操作,而不是期望删除先前关联的信息。

换句话说,当您删除资源时,服务器不再将该 URI 映射到该数据。因此,您不能对它进行 PUT 或 POST 以进行更新,例如“将其标记为未删除”等(请记住,资源被定义为 URI 和一些基础数据之间的映射)。

一些解决方案

由于它明确指出底层数据不一定被删除,因此它不排除服务器将的URI 映射作为 DELETE 实现的一部分,从而有效地制作可以稍后恢复的备份副本。

您可以拥有一个包含所有已删除项目的“/deleted/”集合 - 但您将如何实际执行取消删除?也许最简单的 RESTful 方式是让客户端使用 GET 检索项目,然后将其发布到所需的 URL。

如果您需要能够将已删除的项目恢复到其原始位置怎么办?如果您使用支持它的媒体类型,则可以在对来自 /deleted/ 集合的 GET 的响应中包含原始 URI。然后客户端可以使用它来 POST。这样的响应在 JSON 中可能如下所示:

{
    "original-url":"/some/place/this/was/deleted/from",
    "body":<base64 encoded body>
}

然后,客户端可以将该正文发布到该 URI 以执行取消删除。

或者,如果您的资源定义允许移动的概念(通过更新“位置”属性或类似的东西),那么您可以进行部分更新并避免整个对象的往返。或者,做大多数人做的事情,实现一个类似 RPC 的操作来告诉服务器移动资源!UnRESTful,是的,但它可能在大多数情况下都能正常工作。

你如何决定这些事情

关于如何决定这些事情的问题:您必须考虑删除在您的应用程序上下文中意味着什么,以及您为什么想要它。在许多应用程序中,什么都不会被删除,“删除”实际上只是意味着“从所有进一步的查询/列表等中排除该项目,除非我明确取消删除它”。所以,它实际上只是一个元数据,或者一个移动操作。在那种情况下,为什么还要使用 HTTP DELETE?一个原因可能是如果您想要一个 2 层删除 - 一个可撤消的软或临时版本,以及一个硬/永久版本,嗯......不是。

如果没有任何特定的应用程序上下文,我倾向于像这样实现它们:

为方便起见,我不想再看到此资源发布部分更新以将资源标记为“临时删除”

我不希望任何人能够再访问此资源,因为它令人尴尬/有罪/花费我金钱/等等HTTP DELETE

下一个要考虑的问题是:永久删除是否应该只永久取消映射 URI,以便没有人可以再链接到它,或者是否也有必要清除底层数据?显然,如果您保留数据,那么管理员甚至可以恢复“永久”删除的资源(但不是通过任何 RESTful 接口)。这样做的缺点是,如果数据的所有者真的想要清除它,那么管理员必须在 REST 接口之外执行此操作。

于 2017-09-27T21:38:26.860 回答
5

我认为解决这个问题的最 RESTful 方法是使用 HTTP PUT 将资源标记为删除(和取消删除),然后使用 HTTP DELETE 永久删除资源。要获取标记为删除的资源列表,我将在 HTTP GET 请求中使用一个参数,例如。?state=markedForDeletion. 如果你请求一个没有参数的标记为删除的资源,我认为你应该返回一个“404 Not Found”状态。

于 2011-12-24T00:23:55.837 回答
5

“已删除”(已删除)项目也可以视为资源,对吗?然后我们可以通过以下方式之一访问该资源(例如,对于已删除的用户):

PATCH deleted_users/{id}
PATCH trash/users/{id}
PATCH deleted/users/{id}

或者有些人可能认为这是更宁静的方式:

PATCH deleted/{id}?type=users

在有效载荷中是这样的:

{ deleted_at: null }
于 2018-01-24T01:58:57.633 回答
3

我也遇到了这个问题,我一直在互联网上寻找最好的解决方案。由于我能找到的主要答案对我来说似乎都不正确,因此这是我自己的研究结果。

其他人是正确的,这DELETE是要走的路。您可以包含一个标志来确定它是立即永久DELETE还是移动到垃圾桶(并且可能只有管理员可以执行立即永久DELETE。)

DELETE /api/1/book/33
DELETE /api/1/book/33?permanent

然后后端可以将该书标记为已删除。假设你有一个 SQL 数据库,它可能是这样的:

UPDATE books SET status = 'deleted' WHERE book_id = 33;

正如其他人所提到的,一旦DELETE完成,GET集合的 a 就不会返回该项目。就 SQL 而言,这意味着您必须确保不返回状态为deleted.

SELECT * FROM books WHERE status <> 'deleted';

此外,当您执行 a 时GET /api/1/book/33,您必须返回 404 或 410。410 的一个问题是它意味着 Gone Forever(至少这是我对那个错误代码的理解),所以只要项目存在,我就会返回 404但'deleted'永久删除后标记为和 410。

现在要取消删除,正确的方法是PATCH. 与PUT用于更新项目的 a 不同,PATCH预期是对项目的操作。从我所见,该操作预计将在有效载荷中。为此,需要以某种方式访问​​资源。正如其他人建议的那样,您可以提供一个trashcan区域,该区域一旦删除就会出现该书。像这样的东西可以列出放在垃圾桶里的书:

GET /api/1/trashcan/books

[{"path":"/api/1/trashcan/books/33"}]

因此,结果列表现在将包括书号 33,然后您可以PATCH使用以下操作:

PATCH /api/1/trashcan/books/33

{
    "operation": "undelete"
}

如果您想让操作更加通用,您可以使用以下内容:

PATCH /api/1/trashcan/books/33

{
    "operation": "move",
    "new-path": "/api/1/books/33"
}

然后,“移动”可以在您的界面中尽可能用于 URL 的其他更改。(我正在研究一个 CMS,其中页面的路径在一个名为 的表中tree,每个页面都在另一个名为的表中page并具有一个标识符。我可以通过在tree表中的路径之间移动页面来更改页面的路径!这个是 aPATCH非常有用的地方。)

不幸的是,RFC 没有明确定义PATCH,只是将它与上面所示的操作一起使用,而不是PUT接受表示目标项目的新版本(可能是部分)的有效负载:

PUT /api/1/books/33

{
    "title": "New Title Here"
}

而相应的PATCH(如果您要同时支持两者)将是:

PATCH /api/1/books/33

{
    "operation": "replace",
    "field": "title",
    "value": "New Title Here"
}

我认为支持这么多PATCH操作会很疯狂。但我认为一些很好的例子可以更好地说明为什么PATCH是正确的解决方案。

您可以将其视为:使用 patch 是更改虚拟字段或运行复杂的操作,例如移动,否则需要GET, POST, DELETE(假设DELETE是立即的,您可能会遇到错误并最终导致部分移动...)在某种程度上,PATCH类似于拥有任意数量的方法。UNDELETEor方法会以类似的MOVE方式工作,但 RFC 明确表示有一组标准化的方法,您当然应该坚持使用它们,并且PATCH为您提供了足够的空间而不必添加自己的方法。尽管我在规范中没有看到任何内容说您不应该添加自己的方法。但是,如果您这样做,请确保清楚地记录它们。

于 2018-12-03T17:32:13.150 回答
0

我们已经强制模型创建了一个

POST /modelname/:id/取消删除

于 2019-06-04T15:30:44.983 回答