RESTful восстановить
Это довольно распространенное требование для поддержки отказов или отложенных/пакетных удалений для служб данных. Мне интересно, как реализовать это с помощью RESTful. Я разрывается между несколькими разными вариантами (ни одна из них не кажется мне ужасно привлекательной). Я полагаю, что общий для этих разных вариантов - это потребность в API, который возвращает весь ресурс, помеченный как удаленный для определенного типа ресурса.
Вот некоторые варианты, о которых я думал, и некоторые из их плюсов и минусов:
Параметры для отметки ресурса как удаленные:
- Используйте HTTP DELETE, чтобы пометить ресурс как удаленный.
- Используйте HTTP PUT/POST для обновления удаленного флага. Это не кажется правильным, поскольку он отображает то, что по сути является удалением от метода DELETE HTTP и других методов HTTP.
Параметры, когда GET-ресурс помечен для удаления:
- Возвращает статус HTTP 404 для ресурса, помеченного как удаленный. Чистое и прозрачное, но как мы говорим о разнице между действительно удаленным ресурсом или только что помеченным как удаленное.
- Возврат HTTP-статуса 410. Предоставляет способ сказать разницу, но 410 технически говорит, что "ожидается, что он будет считаться постоянным. Клиенты с возможностями редактирования ссылок ДОЛЖНЫ удалять ссылки на Request-URI после утверждения пользователем". Там может быть достаточно места для маневра в словах "ожидаемый" и "СЛЕДУЕТ" здесь. Не уверен, насколько хорошо 410 поддерживается/понимается там в клиентах.
- Возвращает статус HTTP 200 и включает в себя флаг, указывающий, что ресурс удаляется. Это кажется странным, поскольку идея удалить его в первую очередь была потому, что вы на самом деле хотели, чтобы он не появлялся. Это подталкивает ответственность за фильтрацию удаленных ресурсов до клиентов.
Параметры ответов, которые включают этот удаленный ресурс:
- Опустить ресурсы, созданные как удаленные. Чистый и простой. Но что, если вы действительно хотите узнать об удаленных ресурсах.
- Включите их вместе с полем, указывающим, что они удалены. Это подталкивает ответственность за фильтрацию удаленных ресурсов до клиентов. Это делает сложную разбивку на страницы, если вы хотите показывать только страницы с помощью активных или удаленных ресурсов.
Параметры при обновлении ресурса, помеченного для удаления:
- Использовать HTTP-статус 404. Ресурс пропал? Но как вы можете определить разницу между ресурсом, помеченным как удаленным, и фактически удаленным. Тело HTTP в ответе 404 может быть здесь неоднозначным, но тогда клиенты остаются в синтаксическом анализе или интерпретации вашего тела для устранения неоднозначности. Может быть, заголовок ответа может помочь здесь? Который из? Пользовательский заголовок?
- Используйте HTTP-статус 409 с сообщением о том, как ресурс должен быть сначала восстановлен.
Параметры для восстановления ресурса, помеченного для удаления:
- Используйте HTTP PUT/POST для обновления работы ресурса и снова отмечайте его. Это работает только до тех пор, пока вы не возвращаете HTTP 404 для операции GET для ресурса, так как он не делает с PUT/POST ресурсу, который "не найден" (404).
- Используйте HTTP PUT/POST для создания ресурса. Проблема в том, какие данные имеют приоритет? Данные, отправленные в операции создания? Или данные, которые восстанавливаются? отфильтруйте его из любых других запросов, которые вернули бы его. Затем обработайте HTTP PUT/POST, который создает ресурс как undelete, если идентификатор ресурса указывает на ресурс, помеченный как удаленный.
- Отдельный путь REST, предназначенный для восстановления ресурсов, помеченных для удаления.
Это далеко не полный список. Я просто хотел перечислить некоторые варианты, которые подпрыгивали у меня в голове.
Я знаю, что ответ на вопрос, как это сделать, как обычно, "это зависит". Что мне интересно, какова квалификация/требования, которые вы будете использовать для принятия решения? Как вы видели, как это реализовано или реализовано самостоятельно?
Ответы
Ответ 1
Переход по книге: 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 для обновления ресурса и выполнения.
Ответ 2
Я думаю, что самый RESTful способ решить это - использовать HTTP PUT, чтобы пометить ресурс для удаления (и восстановить его), а затем использовать HTTP DELETE для окончательного удаления ресурса. Чтобы получить список ресурсов, помеченных для удаления, я бы использовал параметр в запросе HTTP GET, например. ?state=markedForDeletion
.
Если вы запрашиваете ресурс, помеченный для удаления без параметра, я думаю, вы должны вернуть статус "404 Not Found".
Ответ 3
Краткая версия
Вы не можете RESTfully восстановить ресурс, используя любой метод на его исходном URI - это нелогично, потому что любая операция, предпринятая на удаленном ресурсе, должна вернуть либо 404, либо 410. Хотя это явно не указано в спецификации, это сильно подразумевается в определении метода DELETE 1 (выделено мной):
По сути, этот метод похож на команду rm в UNIX: он выражает операцию удаления при отображении URI исходного сервера, а не в ожидании удаления ранее связанной информации.
Другими словами, когда у вас есть DELETEd ресурса, сервер больше не сопоставляет этот URI этим данным. Таким образом, вы не можете использовать PUT или POST для обновления, например "отметьте это как undeleted" и т.д. (Помните, что ресурс определяется как сопоставление между URI и некоторыми базовыми данными).
Некоторые решения
Поскольку он явно заявил, что базовые данные не обязательно удаляются, это не исключает, что сервер делает новое сопоставление URI как часть реализации DELETE, тем самым эффективно создавая резервную копию, которую можно восстановить позже.
У вас может быть коллекция "/deleted/", содержащая все удаленные элементы, но как вы на самом деле выполняете восстановление? Возможно, самый простой способ RESTful заключается в том, чтобы клиент извлекал элемент с помощью GET, а затем POST его на нужный URL.
Что делать, если вам нужно восстановить удаленный элемент в исходное местоположение? Если вы используете тип носителя, который его поддерживает, вы можете включить исходный URI в ответ на GET из /deleted/collection. Затем клиент может использовать его для POST. Такой ответ может выглядеть так в JSON:
{
"original-url":"/some/place/this/was/deleted/from",
"body":<base64 encoded body>
}
Клиент мог бы затем отправить этот орган в этот URI для выполнения восстановления.
В качестве альтернативы, если определение вашего ресурса допускает концепцию перемещения (путем обновления свойства "location" или что-то в этом роде), вы можете сделать частичное обновление и избежать кругового путешествия всего объекта. Или сделайте то, что делают большинство людей, и выполните RPC-подобную операцию, чтобы сообщить серверу переместить ресурс! UnRESTful, да, но он, вероятно, будет работать нормально в большинстве ситуаций.
Как вы решаете эти вещи
Что касается вопроса о том, как вы решаете эти вещи: вам нужно подумать о том, что означает удаление в контексте вашего приложения, и почему вы этого хотите. Во многих приложениях ничего не удаляется, а "удалить" на самом деле просто означает "исключить этот элемент из всех последующих запросов/списков и т.д., Если я явно не удалю его". Таким образом, это действительно просто фрагмент метаданных или операция перемещения. В этом случае зачем беспокоиться об удалении HTTP DELETE? Одна из причин может быть, если вы хотите, чтобы 2-уровневое удаление - мягкая или временная версия, которая была отменена, и жесткая/постоянная версия, ну... нет.
При отсутствии какого-либо конкретного контекста приложения я бы склонен реализовать их следующим образом:
Я не хочу видеть этот ресурс больше, для моего удобства: POST частичное обновление, чтобы пометить ресурс как "временно удаленный",
Я не хочу, чтобы кто-нибудь мог получить этот ресурс больше, потому что это смущало/обвиняло/стоило мне денег /etc: HTTP DELETE
Следующий вопрос, который следует рассмотреть, заключается в следующем: должен ли постоянный удалить только unmap URI навсегда, чтобы никто больше не мог ссылаться на него, или же необходимо также очистить базовые данные? Очевидно, что если вы сохраните данные, администратор может восстановить даже "постоянно" удаленный ресурс (но не через любой интерфейс RESTful). Недостатком этого является то, что если владелец данных действительно хочет его очистить, администратор должен сделать это за пределами интерфейса REST.
Ответ 4
Элементы "Удаленные" (разбитые) также могут рассматриваться как ресурс, не так ли? Затем мы можем получить доступ к этому ресурсу одним из этих способов (например, для удаленного пользователя):
PATCH deleted_users/{id}
PATCH trash/users/{id}
PATCH deleted/users/{id}
или некоторые люди могут подумать, что это более спокойный способ:
PATCH deleted/{id}?type=users
и в полезной нагрузке происходит примерно так:
{ deleted_at: null }
Ответ 5
Я также работаю над этой проблемой, и я искал в Интернете то, что кажется лучшим решением. Поскольку ни один из основных ответов, которые я могу найти, не кажется мне правильным, вот мои собственные результаты исследований.
Другие правы, что 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
коллекции не возвращает этот элемент. С точки зрения SQL это означает, что вы должны убедиться, что не вернули элемент со статусом deleted
.
SELECT * FROM books WHERE status <> 'deleted';
Кроме того, когда вы делаете GET/api/1/book/33
, вы должны вернуть 404 или 410. Одна из проблем с 410 заключается в том, что это означает "Унесенные навсегда" (по крайней мере, мое понимание этого кода ошибки), поэтому я бы вернулся 404, пока элемент существует, но помечен как 'deleted'
и 410, как только он был окончательно удален.
Теперь, чтобы восстановить, правильный путь - PATCH
. В отличие от PUT
который используется для обновления элемента, ожидается, что PATCH
будет операцией над элементом. Из того, что я вижу, ожидается, что операция будет в полезной нагрузке. Чтобы это работало, ресурс должен быть доступен каким-то образом. Как предложил кто-то другой, вы можете trashcan
область 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
таблица! Здесь PATCH
очень полезен.)
К сожалению, 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
похож на любое количество методов. Метод UNDELETE
или MOVE
будет работать аналогичным образом, но в RFC четко сказано, что существует набор стандартизированных методов, и вам, безусловно, следует их придерживаться, а PATCH
дает вам достаточно места, чтобы не добавлять свои собственные методы. Хотя в спецификациях я ничего не видел, говоря, что вы не должны добавлять свои собственные методы. Если вы это сделаете, убедитесь, что четко документировали их.
Ответ 6
Мы заставили модель создать
POST/имя модели /: id/восстановить