ОТДЫХ
Это довольно распространенное требование для поддержки удаленных или отложенных / пакетных удалений для служб данных. Что мне интересно, так это как реализовать это RESTful способом. Я разрываюсь между несколькими различными вариантами (ни один из которых не кажется мне ужасно привлекательным). Я полагаю, что для этих различных опций характерна необходимость в API, который возвращает все ресурсы, помеченные как удаленные, для определенного типа ресурса.
Вот некоторые варианты, о которых я думал, и некоторые их плюсы и минусы:
Варианты пометить ресурс как удаленный:
- Используйте HTTP DELETE, чтобы пометить ресурс как удаленный.
- Используйте HTTP PUT/POST для обновления удаленного флага. Это не правильно, поскольку отображает то, что по сути является удалением, из метода HTTP DELETE в другие методы HTTP.
Параметры при получении ресурса, помеченного для удаления:
- Вернуть HTTP-статус 404 для ресурса, помеченного как удаленный. Чисто и прозрачно, но как мы можем отличить действительно удаленный ресурс от ресурса, помеченного как удаленный?
- Возврат HTTP-статуса 410. Предоставляет способ узнать разницу, но технически 410 говорит, что "ожидается, что он будет считаться постоянным. Клиенты с возможностями редактирования ссылок ДОЛЖНЫ удалять ссылки на Request-URI после одобрения пользователя". Здесь может быть достаточно места для маневра в словах "ожидаемый" и "ДОЛЖЕН". Не уверен, насколько хорошо 410 поддерживается / понимается там в клиентах.
- Вернуть HTTP Status 200 и включить поле флага, указывающее, что ресурс удален. Это кажется странным, так как идея его удаления была в первую очередь потому, что вы на самом деле хотели, чтобы он не появлялся. Это возлагает ответственность за фильтрацию удаленных ресурсов на клиентов.
Варианты ответов, которые включают этот удаленный ресурс:
- Опустить ресурсы, макредированные как удаленные. Чистый и простой. Но что, если вы действительно хотите знать об удаленных ресурсах.
- Включите их вместе с полем, указывающим, что они удалены. Это возлагает ответственность за фильтрацию удаленных ресурсов на клиентов. Это затрудняет нумерацию страниц, если вы хотите просматривать только активные или удаленные ресурсы.
Варианты обновления ресурса, помеченного для удаления:
- Использовать HTTP-статус 404. Ресурс ушел? Но как определить разницу между ресурсом, помеченным как удаленный, и ресурсом, фактически удаленным? Тело HTTP в ответе 404 может привести к устранению неоднозначности, но тогда у клиентов останется разбор / интерпретация вашего тела для устранения неоднозначности. Может быть, здесь поможет заголовок ответа? Который из? Пользовательский заголовок?
- Используйте HTTP Status 409 с сообщением о том, как ресурс должен быть сначала восстановлен.
Варианты восстановления ресурса, отмеченного для удаления:
- Используйте HTTP PUT/POST для операции обновления ресурса и снова пометьте его как активный. Это работает только до тех пор, пока вы не возвращаете HTTP 404 для операции GET для ресурса, поскольку с тех пор он не делает PUT / POST для ресурса, который "не найден" (404).
- Используйте HTTP PUT/POST для операции создания ресурса. Проблема в том, какие данные имеют приоритет? Данные отправлены в операции создания? Или данные, которые удаляются? отфильтруйте это от любых других запросов, которые возвратили бы это. Затем обработайте HTTP PUT/POST, который создает ресурс как отмененный, если идентификатор ресурса указывает на ресурс, помеченный как удаленный.
- Отдельный путь REST, выделенный для восстановления ресурсов, помеченных для удаления.
Это ни в коем случае не исчерпывающий список. Я просто хотел перечислить некоторые варианты, которые прыгали в моей голове.
Я знаю ответ, как это сделать, как обычно, "это зависит". Меня интересует, какие квалификации / требования вы бы использовали для принятия решения? Как вы видели, как это реализовано или реализовано?
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.
Когда вы удаляете ресурс, сервер должен пометить ресурс для удаления на его стороне. На самом деле он не должен удалять ресурс, он просто не может дать никакой гарантии, что операция была выполнена. Тем не менее, сервер не должен сказать, что он был удален, когда он этого не сделал.
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, чтобы обновить ресурс и все готово.
Короткая версия
Вы не можете RESTULL восстановить ресурс, используя любой метод на его исходном URI - это нелогично, потому что любая попытка операции с ресурсом, который был удален, должна возвращать либо 404, либо 410. Хотя это явно не указано в спецификации, это явно подразумевается в определении метода DELETE 1 (выделение добавлено):
По сути, этот метод аналогичен команде rm в UNIX: он выражает операцию удаления в сопоставлении URI исходного сервера, а не ожидание удаления ранее связанной информации.
Другими словами, когда вы УДАЛЯЕТЕ ресурс, сервер больше не отображает этот URI на эти данные. Таким образом, вы не можете PUT или POST сделать это, например, "пометить это как удаленное" и т. Д. (Помните, что ресурс определяется как отображение между URI и некоторыми базовыми данными).
Некоторые решения
Поскольку явно указано, что базовые данные не обязательно удаляются, это не мешает серверу создавать новое сопоставление URI как часть реализации DELETE, тем самым эффективно создавая резервную копию, которую можно восстановить позже.
Вы можете иметь коллекцию "/ Удаленный /", которая содержит все удаленные элементы - но как бы вы на самом деле выполняли восстановление? Возможно, самый простой способ RESTful - заставить клиента получить элемент с помощью GET, а затем отправить его по желаемому URL.
Что делать, если вам необходимо восстановить удаленный элемент в его первоначальном месте? Если вы используете тип мультимедиа, который поддерживает его, вы можете включить исходный URI в ответ на GET из / удаленного / коллекции. Затем клиент может использовать его для POST. Такой ответ может выглядеть так в JSON:
{
"original-url":"/some/place/this/was/deleted/from",
"body":<base64 encoded body>
}
Затем клиент может отправить это тело в этот URI для выполнения восстановления.
В качестве альтернативы, если ваше определение ресурса допускает концепцию перемещения (путем обновления свойства "location" или чего-то в этом роде), вы можете выполнить частичное обновление и избежать обхода всего объекта. Или сделайте то, что делает большинство людей, и реализуйте RPC-подобную операцию, чтобы заставить сервер перемещать ресурс! UnRESTful, да, но, вероятно, будет работать нормально в большинстве ситуаций.
Как вы решаете эти вещи
Что касается вопроса о том, как вы решаете эти вещи: вы должны рассмотреть, что означает удаление в контексте вашего приложения, и почему вы этого хотите. Во многих приложениях ничего не удаляется, и "удалить" на самом деле просто означает "исключить этот элемент из всех дальнейших запросов / списков и т. Д., Если я не удаляю его явно". Таким образом, это на самом деле просто часть метаданных или операция перемещения. В таком случае зачем использовать HTTP DELETE? Одной из причин может быть, если вы хотите 2-уровневое удаление - мягкую или временную версию, которую нельзя отменить, и жесткую / постоянную версию, которая, ну... нет.
В отсутствие какого-либо конкретного контекста приложения я был бы склонен реализовать их следующим образом:
Я не хочу больше видеть этот ресурс, для моего удобства: POST частичное обновление, чтобы пометить ресурс как "временно удаленный"
Я не хочу, чтобы кто-нибудь смог больше получить доступ к этому ресурсу, потому что он смущает / компрометирует / стоит мне денег / и т. Д. HTTP DELETE
Следующий вопрос, который следует рассмотреть: должно ли постоянное удаление только постоянно отображать URI, чтобы никто больше не мог ссылаться на него, или же необходимо также удалить базовые данные? Очевидно, что если вы сохраните данные, то администратор сможет восстановить даже "навсегда" удаленный ресурс (однако не через какой-либо интерфейс RESTful). Недостатком этого является то, что если владелец данных действительно хочет его очистить, то администратор должен сделать это за пределами интерфейса REST.
"Удаленные" (уничтоженные) элементы также могут рассматриваться как ресурс, верно? Затем мы можем получить доступ к этому ресурсу одним из следующих способов (например, для удаленного пользователя):
PATCH deleted_users/{id}
PATCH trash/users/{id}
PATCH deleted/users/{id}
или некоторые люди могут подумать, что это более спокойный способ:
PATCH deleted/{id}?type=users
и в полезной нагрузке идет что-то вроде этого:
{ deleted_at: null }
Я думаю, что наиболее RESTful способ решить это - использовать HTTP PUT, чтобы пометить ресурс для удаления (и восстановить), а затем использовать HTTP DELETE, чтобы окончательно удалить ресурс. Чтобы получить список ресурсов, помеченных для удаления, я бы использовал параметр в запросе HTTP GET, например. ?state=markedForDeletion
, Если вы запрашиваете ресурс, помеченный для удаления без параметра, я думаю, вы должны вернуть статус "404 Not Found".
Я также работаю над этой проблемой, и я искал в Интернете то, что кажется лучшим решением. Поскольку ни один из основных ответов, которые я могу найти, не кажется мне правильным, вот мои собственные результаты исследований.
Другие правы, что 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
область, где книга появится после удаления. Примерно так будет работать, чтобы перечислить книги, которые были помещены в корзину:
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
это правильное решение.
Вы можете думать об этом как: использование патча для изменения виртуального поля или выполнения сложной операции, такой как перемещение, которое в противном случае потребовало бы GET
, POST
, DELETE
(и это при условии DELETE
немедленно, и вы можете получить ошибки и в конечном итоге с частичным перемещением...) В некотором смысле, PATCH
похоже на наличие любого количества методов. UNDELETE
или же MOVE
метод будет работать аналогичным образом, но RFC четко говорит, что есть набор стандартизированных методов, и вы, безусловно, должны придерживаться их и PATCH
дает вам много места, чтобы не добавлять свои собственные методы. Хотя в спецификациях я ничего не видел, говоря, что вы не должны добавлять свои собственные методы. Если вы это сделаете, убедитесь, что четко документировали их.
Мы заставили модель создать
POST / имя модели /: идентификатор / восстановить
Другим способом, который лишь слегка нарушил бы принципы REST, было бы выполнение обратимого удаления при вызове метода DELETE ресурсов, а затем окончательное удаление через определенное время. Я реализовал этот подход, и он хорошо работает для приложений, где вы просто хотите предложить пользователю возможность быстро отменить свою операцию.
Моя конкретная реализация заключалась в том, что у меня просто был дополнительный столбец с именем «deletionTimestamp», который был установлен, если ресурс был удален. Затем у меня каждые 24 часа выполнялось задание CRON, которое проверяло время удаления ресурсов, и если время удаления было слишком большим, строка удалялась безвозвратно.
Для меня проблема с фактическим удалением и сохранением копии элемента заключалась в больших накладных расходах на реализацию этого. У меня были ресурсы с очень сложными отношениями внешнего ключа, которые требовали конкретных реализаций для разных типов удаленных ресурсов.