Должен ли я использовать PATCH или PUT в моем REST API?


288

Я хочу спроектировать свою конечную точку отдыха с помощью подходящего метода для следующего сценария.

Есть группа. У каждой группы есть статус. Группа может быть активирована или деактивирована администратором.

Должен ли я спроектировать свою конечную точку как

PUT /groups/api/v1/groups/{group id}/status/activate

ИЛИ

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}

1
Оба в порядке. Но взгляните на RFC для формата JSON PATCH ( tools.ietf.org/html/rfc6902 ). PATCH ожидает получить какой-то документ diff / patch для полезной нагрузки (и необработанный JSON не входит в их число).
Jørn Wildt

1
@ JørnWildt, нет, PUT был бы ужасным выбором. Что ты туда вкладываешь? ПАТЧ - единственный разумный вариант. Что ж, в этом случае вы можете использовать формат PATCH, представленный в вопросе, и просто использовать метод PUT; пример PUT просто неверен.
thecoshman

3
Нет ничего плохого в том, чтобы выставить одно или несколько свойств как автономные ресурсы, которые клиент может ПОЛУЧИТЬ и изменить с помощью PUT. Но да, тогда URL-адрес должен быть / groups / api / v1 / groups / {group id} / status, в который вы можете ПОСТАВИТЬ «активный» или «неактивный» или ПОЛУЧИТЬ, чтобы прочитать текущее состояние.
Jørn Wildt

3
Вот хорошее объяснение того, как на самом деле следует использовать PATCH: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat

5
" activate" не является адекватной конструкцией RESTful. Вероятно, вы пытаетесь statusизменить значение на «активный» или «неактивный». в этом случае вы можете выполнить ПАТЧ .../statusс "активной" или "деактивной" строкой в ​​теле. Или, если вы пытаетесь обновить логическое значение в status.active, вы можете выполнить ПАТЧ .../status/activeс логическим значением в теле
Оги Гарднер

Ответы:


343

Этот PATCHметод является правильным выбором, поскольку вы обновляете существующий ресурс - идентификатор группы. PUTследует использовать только в том случае, если вы полностью заменяете ресурс.

Дополнительная информация о частичной модификации ресурсов доступна в RFC 5789 . В частности, PUTметод описывается следующим образом:

Некоторым приложениям, расширяющим протокол передачи гипертекста (HTTP), требуется функция частичной модификации ресурсов. Существующий метод HTTP PUT позволяет только полную замену документа. Это предложение добавляет новый метод HTTP, PATCH, для изменения существующего ресурса HTTP.


1
Чтобы быть справедливым, вы можете ПОСТАВИТЬ строку «активировать» или «деактивировать» в ресурс. Поскольку (кажется) есть только одна вещь, которую можно переключить, полная ее замена - не такая уж большая проблема. И это позволяет (незначительно) меньший запрос.
thecoshman

37
Важно отметить, что RFC 5789 все еще находится в стадии предложения, не был официально принят и в настоящее время помечен как «irrata exist». Эта «передовая практика» широко обсуждается, и технически PATCH еще не является частью стандарта HTTP.
Fishpen0

4
Всего лишь мои 2 цента несколько лет спустя: вы могли бы рассматривать сам статус как ресурс, и если да, то использование PUT против / status технически заменит ресурс статуса в этой конечной точке.
Джоно Стюарт

3
Я бы осмелился возразить против документов, даже если это «RFC». В документации указано, что вы должны использовать PATCH для изменения только части ресурса, но в нем упущено важное обстоятельство, что метод PATCH определен как неидемпотентный метод. Зачем? Если метод PUT был создан с учетом обновления / замены всего ресурса, то почему метод PATCH не был создан как идемпотентный метод, такой как PUT, если его целью было просто обновить часть ресурса? На мой взгляд, это больше похоже на разницу в идемпотентности обновления, например, «a = 5» (PUT) и «a = a + 5» (PATCH). Оба могут обновлять весь ресурс.
Mladen B.

187

R в REST обозначает ресурс

(Что неверно, потому что это означает «Репрезентативный», но это хороший трюк, чтобы помнить о важности ресурсов в REST).

О себе PUT /groups/api/v1/groups/{group id}/status/activate: вы не обновляете "активировать". «Активировать» - это не вещь, это глагол. Глаголы никогда не бывают хорошими ресурсами. Практическое правило: если действие, глагол, находится в URL-адресе, вероятно, это не RESTful .

Что ты делаешь вместо этого? Либо вы «добавляете», «удаляете» или «обновляете» активацию в группе, либо, если вы предпочитаете: манипулируете «статусом» -ресурса в группе. Лично я бы использовал «активации», потому что они менее двусмысленны, чем понятие «статус»: создание статуса неоднозначно, создание активации - нет.

  • POST /groups/{group id}/activation Создает (или запрашивает создание) активацию.
  • PATCH /groups/{group id}/activationОбновляет некоторые детали существующей активации. Поскольку в группе только одна активация, мы знаем, о каком ресурсе активации идет речь.
  • PUT /groups/{group id}/activationВставляет или заменяет старую активацию. Поскольку в группе только одна активация, мы знаем, о каком ресурсе активации идет речь.
  • DELETE /groups/{group id}/activation Отменит или удалит активацию.

Этот шаблон полезен, когда «активация» группы имеет побочные эффекты, такие как выполнение платежей, отправка писем и так далее. Только POST и PATCH могут иметь такие побочные эффекты. Когда, например, для удаления активации необходимо, например, уведомить пользователей по почте, УДАЛИТЬ - не правильный выбор; в этом случае вы , вероятно , хотите создать деактивацию ресурс : POST /groups/{group_id}/deactivation.

Это хорошая идея - следовать этим рекомендациям, потому что этот стандартный контракт очень ясно дает вашим клиентам, а все прокси и уровни между клиентом и вами знают, когда можно безопасно повторить попытку, а когда нет. Предположим, что клиент находится где-то с нестабильным Wi-Fi, и его пользователь нажимает «деактивировать», что вызывает DELETE: Если это не удается, клиент может просто повторить попытку, пока не получит 404, 200 или что-то еще, с чем он сможет справиться. Но если он запускает, POST to deactivationон знает, что повторять попытку нельзя: это подразумевает POST.
Теперь у любого клиента есть контракт, при соблюдении которого он будет защищать от отправки 42 электронных писем «ваша группа отключена» просто потому, что его HTTP-библиотека продолжала повторять вызовы на бэкэнд.

Обновление одного атрибута: используйте PATCH

PATCH /groups/{group id}

Если вы хотите обновить атрибут. Например, «статус» может быть атрибутом в группах, который можно установить. Такой атрибут, как «статус», часто является хорошим кандидатом для ограничения белого списка значений. В примерах используется некоторая неопределенная JSON-схема:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

При замене ресурса без побочных эффектов используйте PUT.

PUT /groups/{group id}

Если вы хотите заменить всю группу. Это не обязательно означает, что сервер фактически создает новую группу и выбрасывает старую, например, идентификаторы могут остаться прежними. Но для клиентов это может означать PUT : клиент должен предполагать, что он получает совершенно новый элемент, основываясь на ответе сервера.

В случае PUTзапроса клиент должен всегда отправлять весь ресурс, имея все данные, необходимые для создания нового элемента: обычно те же данные, что и для POST-create.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Очень важным требованием является PUTидемпотентность: если вам требуются побочные эффекты при обновлении группы (или изменении активации), вы должны использовать PATCH. Поэтому, когда в результате обновления, например, отправляется электронное письмо, не используйте PUT.


3
Для меня это было очень информативно. «Этот шаблон полезен, когда« активация »группы имеет побочные эффекты» - Почему этот шаблон полезен, особенно в отношении того, когда действия имеют побочные эффекты, в отличие от начальных конечных точек OP
Абдул

1
@Abdul, шаблон полезен по множеству причин, но, учитывая побочные эффекты, клиенту должно быть очень ясно, какие эффекты имеет действие. Когда, скажем, приложение iOS решает отправить всю адресную книгу в виде «контактов», должно быть предельно ясно, какие побочные эффекты имеют создание, обновление, удаление и т. Д. Контакта. Например, чтобы избежать массовой рассылки всех контактов.
berkes

1
В RESTfull PUT также можно изменить идентификатор сущностей - например, идентификатор PrimaryKey, где это может привести к сбою параллельного запроса. (например, при обновлении всей сущности необходимо удалить некоторые строки и добавить новые, что приведет к созданию новых сущностей). Если PATCH никогда не сможет этого сделать, разрешая неограниченное количество запросов PATCH, не затрагивая другие «приложения»
Петр Кула,

1
Очень полезный ответ. Благодарность! Я бы также добавил комментарий, как и в ответе Люка, указав, что разница между PUT / PATCH заключается не только в полном / частичном обновлении, но и в идемпотентности, которая отличается. Это не было ошибкой, это было намеренное решение, и я думаю, что не многие люди принимают это во внимание при принятии решения об использовании метода HTTP.
Mladen B.

1
Сервисы @richremer, как и модели, являются внутренними абстракциями. Так же, как требование отношения 1-1 между конечными точками REST и ORM-моделями или даже таблицами базы данных - плохая абстракция, это плохая абстракция для предоставления услуг. Внешний интерфейс, ваш API, должен сообщать модели предметной области. Как вы их реализуете внутри, API не имеет значения. Вы можете свободно переходить от ActivationService к потоку активации на основе CQRS, не изменяя свой API.
Berkes

13

Я бы рекомендовал использовать PATCH, потому что ваша группа ресурсов имеет много свойств, но в этом случае вы обновляете только поле активации (частичное изменение)

согласно RFC5789 ( https://tools.ietf.org/html/rfc5789 )

Существующий метод HTTP PUT позволяет только полную замену документа. Это предложение добавляет новый метод HTTP, PATCH, для изменения существующего ресурса HTTP.

Также, более подробно,

Разница между запросами PUT и PATCH отражается в том, как сервер обрабатывает закрытый объект для изменения ресурса,
идентифицированного Request-URI. В запросе PUT закрытый объект считается измененной версией ресурса, хранящегося на
исходном сервере, и клиент запрашивает
замену сохраненной версии . Однако с помощью PATCH закрытый объект содержит набор инструкций, описывающих, как ресурс, в настоящее время находящийся на нем, может быть создан или изменен уже существующим путем применения PATCH.
исходном сервере, должен быть изменен для создания новой версии. Метод PATCH влияет на ресурс, идентифицированный Request-URI, а
также МОЖЕТ иметь побочные эффекты для других ресурсов; т.е. новые ресурсы

PATCH не является ни безопасным, ни идемпотентным, как определено в [RFC2616], раздел 9.1.

Клиенты должны выбирать, когда использовать PATCH, а не PUT. Для
примера, если размер заплаты документа больше размера
новых данных о ресурсах , которые будут использованы в PUT, то это может сделать
смысл использовать PUT вместо пластыря. Сравнение с POST еще сложнее, потому что POST используется по-разному и может
включать в себя операции, подобные PUT и PATCH, если сервер того пожелает. Если
операция не изменяет ресурс, указанный в Request-URI, предсказуемым образом, следует рассматривать POST вместо PATCH
или PUT.

Код ответа для PATCH:

Код ответа 204 используется, потому что ответ не содержит тела сообщения (которое будет иметь ответ с кодом 200). Обратите внимание, что можно использовать и другие коды успеха.

также см. http://restcookbook.com/HTTP%20Methods/patch/

Предостережение: API, реализующий PATCH, должен обновлять атомарно. НЕ ДОЛЖНО быть возможным, чтобы ресурсы были исправлены наполовину по запросу GET.


7

Поскольку вы хотите разработать API с использованием архитектурного стиля REST, вам необходимо подумать о своих сценариях использования, чтобы решить, какие концепции достаточно важны для использования в качестве ресурсов. Если вы решите раскрыть статус группы в качестве подресурса, вы можете дать ей следующий URI и реализовать поддержку методов GET и PUT:

/groups/api/groups/{group id}/status

Обратной стороной этого подхода по сравнению с PATCH для модификации является то, что вы не сможете вносить изменения более чем в одно свойство группы атомарно и транзакционно. Если транзакционные изменения важны, используйте PATCH.

Если вы решите раскрыть статус как подресурс группы, это должна быть ссылка в представлении группы. Например, если агент получает группу 123 и принимает XML, тело ответа может содержать:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

Гиперссылка необходима для выполнения гипермедиа в качестве движка состояния приложения в архитектурном стиле REST.


0

Я бы предпочел что-то попроще, например activate/ deactivatesub-resource (связанный Linkзаголовком с rel=service).

POST /groups/api/v1/groups/{group id}/activate

или

POST /groups/api/v1/groups/{group id}/deactivate

Для потребителя этот интерфейс предельно прост и следует принципам REST, не отвлекая вас от концептуализации «активаций» как отдельных ресурсов.


0

Один из возможных вариантов реализации такого поведения:

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

И, очевидно, если кому-то нужно его деактивировать, он PUTбудет иметь Deactivatedстатус в JSON.

В случае необходимости массовой активации / деактивации, PATCHможет перейти в игру (не для конкретной группы, а для groupsресурса:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

В общем, это идея, как предлагает @Andrew Dobrowolski, но с небольшими изменениями в точной реализации.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.