REST API - PUT vs PATCH с примерами из реальной жизни
Прежде всего, некоторые определения:
PUT определен в разделе 9.6 RFC 2616:
Метод PUT запрашивает, чтобы вложенный объект был сохранен под предоставленным Request-URI. Если Request-URI ссылается на уже существующий ресурс, вложенный объект СЛЕДУЕТ рассматривать как модифицированную версию, находящуюся на исходном сервере. Если Request-URI не указывает на существующий ресурс, и этот URI может быть определен как новый ресурс запрашивающим пользовательским агентом, сервер-источник может создать ресурс с этим URI.
PATCH определен в RFC 5789:
Метод PATCH запрашивает, чтобы набор изменений, описанный в объекте запроса, был применен к ресурсу, идентифицированному Request-URI.
Также согласно RFC 2616, раздел 9.1.2. PUT является идемпотентным, а PATCH - нет.
Теперь давайте посмотрим на реальный пример. Когда я делаю POST, чтобы /users
с данными {username: 'skwee357', email: 'skwee357@domain.com'}
и сервер способен создавать ресурс, он ответит 201 и местоположение ресурса (предположим, /users/1
) и любой следующий звонок в GET /users/1
вернусь {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}
,
Теперь допустим, что я хочу изменить свою электронную почту. Модификация электронной почты считается "набором изменений", и поэтому я должен PATCH /users/1
с " патчем документа". В моем случае это будет JSON {email: 'skwee357@newdomain.com'}
, Затем сервер возвращает 200 (при условии, что разрешение в порядке). Это подводит меня к первому вопросу:
- Патч НЕ идемпотентен. Об этом говорится в RFC 2616 и RFC 5789. Однако, если я выполню один и тот же запрос PATCH (с моим новым адресом электронной почты), я получу то же состояние ресурса (с изменением моего адреса электронной почты до запрошенного значения). Почему не патч тогда идемпотент?
PATCH - это относительно новый глагол (RFC введен в марте 2010 года), и он решает проблему "исправления" или изменения набора полей. До появления PATCH все использовали PUT для обновления ресурса. Но после того, как был введен PATCH, я не могу понять, для чего тогда используется PUT? И это подводит меня ко второму (и основному) вопросу:
- В чем реальная разница между PUT и PATCH? Я где-то читал, что PUT может использоваться для замены всей сущности под конкретным ресурсом, поэтому нужно отправить полную сущность (вместо набора атрибутов, как с PATCH). Каково реальное практическое использование для такого случая? Когда вы хотите заменить / перезаписать объект под определенным URI ресурса и почему такая операция не рассматривается как обновление / исправление объекта? Единственный практический вариант использования PUT, который я вижу, - это выдача PUT при сборе, т.е.
/users
заменить всю коллекцию. Выдача PUT для конкретного объекта не имеет смысла после того, как был введен PATCH. Я ошибся?
16 ответов
ПРИМЕЧАНИЕ: Когда я впервые потратил время на чтение о REST, идемпотентность была непонятной концепцией, чтобы попытаться понять правильно. Я все еще не совсем понял в своем первоначальном ответе, как показали дальнейшие комментарии (и ответ Джейсона Хетгера). Некоторое время я сопротивлялся обновлению этого ответа, чтобы избежать эффективного плагиата Джейсона, но сейчас я его редактирую, потому что, ну, меня попросили (в комментариях).
Прочитав мой ответ, я предлагаю вам также прочитать превосходный ответ Джейсона Хетгера на этот вопрос, и я постараюсь сделать свой ответ лучше, не просто крадя у Джейсона.
Почему PUT идемпотент?
Как вы отметили в цитате RFC 2616, PUT считается идемпотентным. Когда вы кладете ресурс, в игру вступают два следующих предположения:
Вы имеете в виду сущность, а не коллекцию.
Предоставляемая вами сущность завершена (вся сущность).
Давайте посмотрим на один из ваших примеров.
{ "username": "skwee357", "email": "skwee357@domain.com" }
Если вы разместите этот документ в /users
, как вы предлагаете, тогда вы можете получить обратно такой объект, как
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
Если вы хотите изменить эту сущность позже, вы выбираете между PUT и PATCH. PUT может выглядеть так:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
Вы можете сделать то же самое, используя PATCH. Это может выглядеть так:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
Вы сразу заметите разницу между этими двумя. PUT включал все параметры этого пользователя, но PATCH включал только тот, который был изменен (email
).
При использовании PUT предполагается, что вы отправляете полный объект, и этот полный объект заменяет любой существующий объект с этим URI. В приведенном выше примере PUT и PATCH выполняют одну и ту же цель: они оба меняют адрес электронной почты этого пользователя. Но PUT обрабатывает это, заменяя весь объект, а PATCH обновляет только те поля, которые были предоставлены, оставляя остальные в покое.
Поскольку запросы PUT включают всю сущность, если вы выполняете один и тот же запрос несколько раз, он всегда должен иметь одинаковый результат (отправленные вами данные теперь являются полными данными сущности). Поэтому PUT является идемпотентом.
Неправильное использование PUT
Что произойдет, если вы используете вышеуказанные данные PATCH в запросе PUT?
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(Я предполагаю, что для целей этого вопроса сервер не имеет каких-либо конкретных обязательных полей и позволил бы этому случиться... что в действительности может быть не так).
Так как мы использовали PUT, но только поставляли email
Теперь это единственное, что есть в этой сущности. Это привело к потере данных.
Этот пример здесь для иллюстративных целей - никогда не делайте этого на самом деле. Этот запрос PUT технически идемпотентен, но это не значит, что это не ужасная, сломанная идея.
Как PATCH может быть идемпотентом?
В приведенном выше примере PATCH был идемпотентным. Вы внесли изменение, но если вы вносите одно и то же изменение снова и снова, оно всегда будет возвращать один и тот же результат: вы изменили адрес электронной почты на новое значение.
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // email address was changed
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address... again
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // nothing changed since last GET
}
Мой оригинальный пример, исправленный на точность
Первоначально у меня были примеры, которые, как мне казалось, демонстрировали неидемпотентность, но они вводили в заблуждение / неверны. Я собираюсь сохранить примеры, но использую их, чтобы проиллюстрировать другую вещь: то, что несколько документов PATCH против одной и той же сущности, изменяя различные атрибуты, не делают PATCH неидемпотентными.
Допустим, что в свое время пользователь был добавлен. Это состояние, с которого вы начинаете.
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
После патча у вас есть измененный объект:
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // the email changed, yay!
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Если вы затем несколько раз примените свой PATCH, вы продолжите получать тот же результат: электронное письмо было изменено на новое значение. А входит, А выходит, поэтому это идемпотент.
Через час, после того, как вы пошли выпить кофе и сделать перерыв, кто-то другой приходит со своим собственным патчем. Кажется, Почта вносит некоторые изменения.
PATCH /users/1
{"zip": "12345"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // still the new email you set
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345" // and this change as well
}
Поскольку этот патч из почтового отделения не связан с электронной почтой, только почтовый индекс, если он применяется повторно, он также получит тот же результат: почтовый индекс установлен на новое значение. А входит, А выходит, поэтому это тоже идемпотент.
На следующий день вы решили отправить свой патч снова.
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
Ваш патч имеет тот же эффект, что и вчера: он установил адрес электронной почты. А вошел, А вышел, поэтому это тоже идемпотент.
Что я ошибся в своем первоначальном ответе
Я хочу провести важное различие (что-то я ошибся в своем первоначальном ответе). Многие серверы будут отвечать на ваши запросы REST, отправляя обратно новое состояние объекта с вашими изменениями (если таковые имеются). Таким образом, когда вы получаете этот ответ обратно, он отличается от того, который вы получили вчера, потому что почтовый индекс не тот, который вы получили в прошлый раз. Однако ваш запрос касался не почтового индекса, а только электронной почты. Таким образом, ваш документ PATCH все еще идемпотентен - электронное письмо, которое вы отправили в PATCH, теперь является адресом электронной почты объекта.
Так когда же патч не идемпотентен, тогда?
Для полного рассмотрения этого вопроса я снова отсылаю вас к ответу Джейсона Хетгера. Я просто собираюсь на этом остановиться, потому что, честно говоря, не думаю, что смогу ответить на этот вопрос лучше, чем он уже.
Хотя превосходный ответ Дана Лоу очень подробно ответил на вопрос OP о разнице между PUT и PATCH, его ответ на вопрос, почему PATCH не идемпотентен, не совсем корректен.
Чтобы показать, почему PATCH не идемпотентен, полезно начать с определения идемпотентности (из Википедии):
Термин идемпотент используется более всесторонне для описания операции, которая будет давать одинаковые результаты при выполнении один или несколько раз [...] Идемпотентная функция - это функция, которая имеет свойство f(f(x)) = f(x) для любое значение х.
На более доступном языке идемпотентный PATCH может быть определен как: После PATCHing ресурса с документом исправления все последующие вызовы PATCH к тому же ресурсу с тем же документом исправления не изменят ресурс.
И наоборот, неидемпотентная операция - это операция, в которой f(f(x))!= F (x), которая для PATCH может быть сформулирована как: После PATCHing ресурса с документом исправления, последующие PATCH вызывают тот же ресурс с тот же патч-документ действительно меняет ресурс.
Чтобы проиллюстрировать неидемпотентный PATCH, предположим, что существует ресурс /users, и предположим, что вызов GET /users
возвращает список пользователей, на данный момент:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]
Предположим, что вместо PATCHing /users/{id}, как в примере OP, сервер разрешает PATCHing /users. Давайте выполним этот запрос PATCH:
PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]
Наш патч-документ указывает серверу добавить нового пользователя с именем newuser
в список пользователей. После первого звонка GET /users
вернется:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
{ "id": 2, "username": "newuser", "email": "newuser@example.org" }]
Теперь, если мы выдадим тот же самый запрос PATCH, что и выше, что произойдет? (Для примера рассмотрим, что ресурс /users допускает дублирование имен пользователей.) "Op" - это "add", поэтому новый пользователь добавляется в список, а затем GET /users
возвращает:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
{ "id": 2, "username": "newuser", "email": "newuser@example.org" },
{ "id": 3, "username": "newuser", "email": "newuser@example.org" }]
Ресурс /users снова изменился, хотя мы выдавали одинаковый PATCH для той же конечной точки. Если наш PATCH равен f(x), то f (f (x)) не совпадает с f(x), и, следовательно, этот конкретный PATCH не идемпотентен.
Хотя PATCH не гарантированно является идемпотентным, в спецификации PATCH нет ничего, что мешало бы вам выполнять все операции PATCH на вашем конкретном сервере. RFC 5789 даже ожидает преимуществ от идемпотентных запросов PATCH:
Запрос PATCH может быть выдан таким образом, чтобы быть идемпотентным, что также помогает предотвратить плохие исходы от коллизий между двумя запросами PATCH на одном и том же ресурсе в течение аналогичного периода времени.
В примере Дэна его операция PATCH фактически идемпотентна. В этом примере объект /users/1 изменился между нашими запросами PATCH, но не из-за наших запросов PATCH; это был фактически другой патч Почтового отделения, который вызвал изменение почтового индекса. Другой PATCH Почты - это другая операция; если наш PATCH - f(x), то PATCH почтового отделения - g(x). Идемпотентность утверждает, что f(f(f(x))) = f(x)
, но не дает никаких гарантий о f(g(f(x)))
,
TL; DR - упрощенная версия
PUT => Установить все новые атрибуты для существующего ресурса.
PATCH => Частично обновить существующий ресурс (требуются не все атрибуты).
Мне тоже было любопытно, и я нашел несколько интересных статей. Я не могу ответить на ваш вопрос в полной мере, но это, по крайней мере, дает дополнительную информацию.
http://restful-api-design.readthedocs.org/en/latest/methods.html
HTTP RFC указывает, что PUT должен принимать полное новое представление ресурса в качестве объекта запроса. Это означает, что если, например, предоставляются только определенные атрибуты, их следует удалить (т. Е. Установить на ноль).
Учитывая это, PUT должен отправить весь объект. Например,
/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}
Это будет эффективно обновлять электронную почту. Причина, по которой PUT не может быть слишком эффективной, заключается в том, что ваше единственное изменение одного поля и включение имени пользователя бесполезно. Следующий пример показывает разницу.
/users/1
PUT {id: 1, email: 'newemail@domain.com'}
Теперь, если PUT был разработан в соответствии со спецификацией, тогда PUT установит имя пользователя в null, и вы получите следующее.
{id: 1, username: null, email: 'newemail@domain.com'}
Когда вы используете PATCH, вы обновляете только указанное вами поле, а остальные оставляете в покое, как в вашем примере.
Следующий взгляд на патч немного отличается от того, что я никогда раньше не видел.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Разница между запросами PUT и PATCH отражается в способе, которым сервер обрабатывает вложенный объект для изменения ресурса, идентифицируемого Request-URI. В запросе PUT вложенный объект считается модифицированной версией ресурса, хранящегося на исходном сервере, и клиент запрашивает замену сохраненной версии. Однако с помощью PATCH вложенный объект содержит набор инструкций, описывающих, как ресурс, находящийся в данный момент на исходном сервере, должен быть модифицирован для создания новой версии. Метод PATCH влияет на ресурс, идентифицируемый Request-URI, и он также МОЖЕТ иметь побочные эффекты на других ресурсах; то есть новые ресурсы могут быть созданы или существующие изменены путем применения PATCH.
PATCH /users/123
[
{ "op": "replace", "path": "/email", "value": "new.email@example.org" }
]
Вы более или менее рассматриваете PATCH как способ обновления поля. Таким образом, вместо отправки частичного объекта вы отправляете операцию. т.е. заменить электронную почту на значение.
Статья заканчивается этим.
Стоит отметить, что PATCH на самом деле не предназначен для действительно REST API, поскольку диссертация Филдинга не определяет какой-либо способ частичной модификации ресурсов. Но сам Рой Филдинг сказал, что PATCH был чем-то [он] создан для первоначального предложения HTTP/1.1, потому что частичное PUT никогда не бывает RESTful. Конечно, вы не передаете полное представление, но REST не требует, чтобы представления были полными в любом случае.
Теперь я не знаю, согласен ли я со статьей, как отмечают многие комментаторы. Отправка по частичному представлению может легко быть описанием изменений.
Для меня я использую PATCH. По большей части я буду рассматривать PUT как PATCH, поскольку единственное реальное различие, которое я заметил до сих пор, заключается в том, что PUT "должен" установить отсутствующие значения на ноль. Возможно, это не самый правильный способ сделать это, но удачное кодирование идеально.
tl;dr версия
POST: используется для создания объекта
PUT: используется для обновления объекта, где вы должны отправить полное представление объекта, как вы хотите, чтобы оно сохранялось.
PATCH: используется для обновления объекта, в котором вы отправляете только те поля, которые необходимо обновить.
Разница между PUT и PATCH заключается в том, что:
- PUT должен быть идемпотентом. Чтобы достичь этого, вы должны поместить весь полный ресурс в тело запроса.
- PATCH может быть неидемпотентным. Это подразумевает, что он также может быть идемпотентным в некоторых случаях, например в описанных вами случаях.
Для PATCH требуется некоторый "язык патчей", чтобы сообщить серверу, как модифицировать ресурс. Вызывающий и сервер должны определить некоторые "операции", такие как "добавить", "заменить", "удалить". Например:
GET /contacts/1
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"state": "NY",
"zip": "10001"
}
PATCH /contacts/1
{
[{"operation": "add", "field": "address", "value": "123 main street"},
{"operation": "replace", "field": "email", "value": "abc@myemail.com"},
{"operation": "delete", "field": "zip"}]
}
GET /contacts/1
{
"id": 1,
"name": "Sam Kwee",
"email": "abc@myemail.com",
"state": "NY",
"address": "123 main street",
}
Вместо использования явных полей "операция", язык патчей может сделать это неявным путем определения соглашений, таких как:
в теле запроса PATCH:
- Существование поля означает "заменить" или "добавить" это поле.
- Если значение поля равно нулю, это означает удаление этого поля.
С вышеупомянутым соглашением PATCH в примере может принять следующую форму:
PATCH /contacts/1
{
"address": "123 main street",
"email": "abc@myemail.com",
"zip":
}
Который выглядит более кратким и удобным. Но пользователи должны знать о базовом соглашении.
С операциями, которые я упомянул выше, PATCH все еще идемпотентен. Но если вы определите такие операции, как: "increment" или "append", вы легко увидите, что он больше не будет идемпотентным.
По моему скромному мнению, идемпотенция означает:
- ПОЛОЖИТЬ:
Я отправляю определение конкурирующего ресурса, поэтому итоговое состояние ресурса точно такое, как определено параметрами PUT. Каждый раз, когда я обновляю ресурс одними и теми же параметрами PUT, конечное состояние точно такое же.
- ПАТЧ:
Я отправил только часть определения ресурса, поэтому может случиться так, что другие пользователи тем временем обновляют ДРУГИЕ параметры этого ресурса. Следовательно, последовательные патчи с одинаковыми параметрами и их значениями могут привести к разному состоянию ресурса. Например:
Предположим, что объект определяется следующим образом:
АВТО: - цвет: черный, - тип: седан, - мест: 5
Я исправляю это:
{красный цвет'}
Результирующий объект:
АВТО: - цвет: красный, - тип: седан, - мест: 5
Затем некоторые другие пользователи устанавливают на эту машину следующие патчи:
{type: 'хэтчбек'}
Итак, получившийся объект:
АВТО: - цвет: красный, - тип: хэтчбек, - мест: 5
Теперь, если я снова исправлю этот объект:
{красный цвет'}
результирующий объект:
АВТО: - цвет: красный, - тип: хэтчбек, - мест: 5
Чем отличается от того, что у меня было раньше!
Вот почему PATCH не идемпотентен, а PUT идемпотентен.
Возможно, я немного не по теме, учитывая ваши вопросы об идемпотентности, но я бы хотел, чтобы вы рассмотрели эволютивность.
Предположим, у вас есть следующий элемент:
{
"username": "skwee357",
"email": "skwee357@domain.example"
}
Если вы изменяете с помощью PUT, вы должны дать полное представление объекта:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@newdomain.example"
}
Теперь вы обновляете схему и добавляете поле:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@newdomain.example",
"phone": "123-456-7890"
}
Теперь обновите его снова с помощью PUT таким же образом, он установитphone
обнулить. Чтобы избежать этого плохого побочного эффекта, вы должны обновлять все компоненты, которые изменяют элементы, каждый раз, когда вы обновляете свою схему. Хромой.
При использовании PATCH у вас нет этой проблемы, потому что PATCH обновляет только заданные поля. Итак, на мой взгляд, вы должны использовать PATCH для изменения элемента (независимо от того, действительно ли он идемпотент или нет). Это реальный возврат опыта.
TLDR;
POST ➔ Создает ресурс ( НЕ ИДЕМПОТЕНТНЫЙ — несколько раз вызов одного и того же API может конфликтовать с уже существующим ресурсом).
PUT ➔ Полностью обновляет ресурс — должны быть предоставлены все атрибуты ( IDEMPOTENT — можно безопасно вызывать несколько раз, поскольку ресурс будет полностью заменен)
ИСПРАВЛЕНИЕ ➔ Частично обновите существующий ресурс — должны быть предоставлены только необходимые атрибуты ( НЕ IDEMPOTENT — многократный вызов может изменить исходный ресурс. Например, обновление массива новым элементом всегда увеличит массив, а put заменит его тем, что предоставлено. Еще одним недостатком PATCH является то, что ему необходимо поддерживать новую логику, контракт на обновление только некоторых полей.
Все остальные ответили на PUT vs PATCH. Я просто собирался ответить, какая часть заголовка исходного вопроса задает вопрос: "... в сценариях реальной жизни REST API". В реальном мире это случилось со мной с интернет-приложением, которое имело сервер RESTful и реляционную базу данных с таблицей Customer, которая была "широкой" (около 40 столбцов). Я по ошибке использовал PUT, но предположил, что это похоже на команду обновления SQL, и не заполнил все столбцы. Проблемы: 1) Некоторые столбцы были необязательными (поэтому пустое поле было правильным ответом), 2) многие столбцы менялись редко, 3) некоторые столбцы, которые пользователю не разрешалось изменять, например, отметка времени даты последней покупки, 4) один столбец был свободным -формировать текстовый столбец "Комментарии", который пользователи старательно заполняют комментариями службы поддержки клиентов на полстраницы, такими как имена супругов, чтобы спросить о обычном заказе ИЛИ5) В свое время я работал над интернет-приложением и беспокоился о размере пакета.
Недостатком PUT является то, что он заставляет вас отправлять большой пакет информации (все столбцы, включая весь столбец комментариев, хотя изменилось только несколько вещей) И многопользовательская проблема, когда 2+ пользователей одновременно редактируют одного и того же клиента (последний один, чтобы нажать Обновить, победит). Недостатком PATCH является то, что вам нужно отслеживать на стороне просмотра / экрана, что изменилось, и иметь некоторый интеллект, чтобы отправлять только те части, которые изменились. Многопользовательская проблема патча ограничивается редактированием одного и того же столбца (столбцов) одного и того же клиента.
Позвольте мне процитировать и прокомментировать более подробно RFC 7231 раздел 4.2.2, который уже упоминался в предыдущих комментариях:
Метод запроса считается "идемпотентным", если предполагаемое влияние на сервер нескольких идентичных запросов с помощью этого метода такое же, как и эффект для одного такого запроса. Из методов запроса, определенных в этой спецификации, PUT, DELETE и безопасные методы запроса являются идемпотентными.
(...)
Идемпотентные методы отличаются тем, что запрос может повторяться автоматически, если сбой связи происходит до того, как клиент сможет прочитать ответ сервера. Например, если клиент отправляет запрос PUT и основное соединение закрывается до получения какого-либо ответа, тогда клиент может установить новое соединение и повторить идемпотентный запрос. Он знает, что повторение запроса будет иметь тот же предполагаемый эффект, даже если исходный запрос был выполнен успешно, хотя ответ может отличаться.
Итак, что должно быть "одинаковым" после повторного запроса идемпотентного метода? Не состояние сервера и не ответ сервера, а предполагаемый эффект. В частности, метод должен быть идемпотентным "с точки зрения клиента". Теперь я думаю, что эта точка зрения показывает, что последний пример ответа Дэна Лоу, который я не хочу здесь описывать, действительно показывает, что запрос PATCH может быть неидемпотентным (более естественным образом, чем пример в Ответ Джейсона Хетгера).
Действительно, давайте сделаем пример немного более точным, сделав одно возможное намерение для первого клиента. Допустим, этот клиент просматривает список пользователей проекта, чтобы проверить их электронные письма и почтовые индексы. Он начинает с пользователя 1, замечая, что почтовый индекс верен, а адрес электронной почты неправильный. Он решает исправить это с помощью запроса PATCH, который является полностью законным, и отправляет только
PATCH /users/1
{"email": "skwee357@newdomain.com"}
так как это единственное исправление. Теперь запрос не выполняется из-за какой-либо проблемы в сети и автоматически отправляется через пару часов. В то же время другой клиент (ошибочно) изменил почтовый индекс пользователя 1. Затем, отправка того же самого запроса PATCH во второй раз не приводит к ожидаемому эффекту клиента, поскольку в результате мы получаем неправильный почтовый индекс. Следовательно, метод не идемпотентен в смысле RFC.
Если вместо этого клиент использует запрос PUT для исправления электронной почты, отправляя на сервер все свойства пользователя 1 вместе с электронной почтой, его ожидаемый эффект будет достигнут, даже если запрос необходимо будет повторно отправить позже, а пользователь 1 был изменен. тем временем --- поскольку второй запрос PUT перезапишет все изменения, начиная с первого запроса.
В заключение обсуждения идемпотентности я должен отметить, что можно определить идемпотентность в контексте REST двумя способами. Давайте сначала формализуем несколько вещей:
Ресурс является функцией с его кообластью является классом строк. Другими словами, ресурс - это подмножествоString × Any
, где все ключи уникальны. Назовем класс ресурсовRes
.
Операция REST для ресурсов - это функция f(x: Res, y: Res): Res
. Два примера операций REST:
PUT(x: Res, y: Res): Res = x
, а такжеPATCH(x: Res, y: Res): Res
, который работает какPATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.
(Это определение специально разработано, чтобы спорить о PUT
а также POST
, и, например, не имеет особого смысла на GET
а также POST
, так как его не волнует настойчивость).
Теперь, исправив x: Res
(информативно, используя каррирование), PUT(x: Res)
а также PATCH(x: Res)
одномерные функции типа Res → Res
.
Функция
g: Res → Res
называется глобально идемпотентным, когдаg ○ g == g
, т.е. для любогоy: Res
,g(g(y)) = g(y)
.Позволять
x: Res
ресурс, иk = x.keys
. Функцияg = f(x)
называется левым идемпотентным, когда для каждогоy: Res
, у нас естьg(g(y))|ₖ == g(y)|ₖ
. По сути, это означает, что результат должен быть таким же, если мы посмотрим на примененные ключи.
Так, PATCH(x)
не является глобально идемпотентным, но идемпотентным слева. И здесь имеет значение левая идемпотентность: если мы исправляем несколько ключей ресурса, мы хотим, чтобы эти ключи были такими же, если мы исправим его снова, и нас не заботит остальная часть ресурса.
И когда RFC говорит о том, что PATCH не является идемпотентным, он говорит о глобальной идемпотентности. Что ж, хорошо, что он не идемпотентен глобально, иначе это была бы неработающая операция.
Теперь ответ Джейсона Хётгера пытается продемонстрировать, что PATCH даже не является идемпотентным, но он нарушает слишком много вещей, чтобы это сделать:
- Прежде всего, PATCH используется в наборе, хотя PATCH определен для работы с картами / словарями / объектами "ключ-значение".
- Если кто-то действительно хочет применить PATCH к наборам, то следует использовать естественный перевод:
t: Set<T> → Map<T, Boolean>
, определенный сx in A iff t(A)(x) == True
. Используя это определение, исправление остается идемпотентным. - В этом примере этот перевод не использовался, вместо этого PATCH работает как POST. Прежде всего, почему для объекта создается идентификатор? А когда он образуется? Если объект сначала сравнивается с элементами набора, и если соответствующий объект не найден, то создается идентификатор, затем снова программа должна работать по-другому (
{id: 1, email: "me@site.com"}
должен совпадать с{email: "me@site.com"}
, иначе программа всегда будет сломана и ПАТЧ не сможет исправить). Если идентификатор создается до проверки набора, снова программа не работает.
Можно привести примеры неидемпотентности PUT, сломав половину того, что сломано в этом примере:
- Примером сгенерированных дополнительных функций может быть управление версиями. Можно вести учет количества изменений на одном объекте. В этом случае PUT не идемпотентен:
PUT /user/12 {email: "me@site.com"}
приводит к{email: "...", version: 1}
в первый раз и{email: "...", version: 2}
второй раз. - Возня с идентификаторами, можно генерировать новый идентификатор каждый раз, когда объект обновляется, что приводит к неидемпотентному PUT.
Все приведенные выше примеры являются естественными примерами, с которыми можно встретиться.
Мое последнее замечание: PATCH не должен быть глобально идемпотентным, иначе не даст желаемого эффекта. Вы хотите изменить адрес электронной почты своего пользователя, не затрагивая остальную информацию, и не хотите перезаписывать изменения, внесенные другой стороной, имеющей доступ к тому же ресурсу.
Метод PUT идеально подходит для обновления данных в табличном формате, например, в реляционной базе данных или объекте, таком как хранилище. В зависимости от варианта использования его можно использовать для частичного обновления данных или замены объекта в целом. Это всегда будет идемпотентом.
Метод PATCH можно использовать для обновления (или реструктуризации) данных в формате json или xml, которые хранятся в локальной файловой системе или в базе данных sql. Это можно сделать, указав действие/операцию, которые необходимо выполнить в запросе, например, добавление/удаление/перемещение пары ключ-значение в объект json. Операция удаления может использоваться для удаления пары ключ-значение, а повторный запрос приведет к ошибке, поскольку ключ был удален ранее, что делает его неидемпотентным методом. обратитесь к RFC 6902 для запроса исправления данных json.
Эта статья содержит подробную информацию о методе PATCH.
Я попытаюсь обобщить в терминах непрофессионала, что я понял (может быть, это поможет)
Патч не является полностью идемпотентным (это может быть в идеальной ситуации, когда никто не меняет другое поле вашей сущности).
В неидеальной (реальной) ситуации кто-то изменяет другое поле вашего объекта с помощью другой операции Patch, и тогда обе операции не являются идемпотентными (это означает, что ресурс, который вы оба изменяете, возвращается «неправильным» с любой точки зрения)
Таким образом, вы не можете назвать его идемпотентным, если он не охватывает 100% ситуаций. Может, для кого-то это не так важно, а для кого-то
Здесь очень хорошее объяснение -
Нормальная полезная нагрузка-// Дом на участке 1 {адрес: "участок 1", владелец: "сегун", тип: "дуплекс", цвет: "зеленый", комнаты: "5", кухни: "1", окна: 20}PUT For Updated-// PUT запрос полезной нагрузки для обновления окон Дома на участке 1 {адрес: 'участок 1', владелец: 'сегун', тип: 'дуплекс', цвет: 'зеленый', комнаты: '5', kitchens: '1', windows: 21 } Примечание. В приведенной выше полезной нагрузке мы пытаемся обновить окна с 20 до 21.
Теперь посмотрите полезные данные PATH payload- // Patch request для обновления окон в доме { windows: 21 }
Поскольку PATCH не идемпотентен, неудавшиеся запросы не повторяются автоматически в сети. Кроме того, если запрос PATCH сделан на несуществующий URL-адрес, например, при попытке заменить входную дверь несуществующего здания, он должен просто потерпеть неудачу без создания нового ресурса, в отличие от PUT, который создаст новый с использованием полезной нагрузки. Если подумать, будет странно иметь одинокую дверь по адресу дома.
Одна дополнительная информация, которую я хочу добавить, заключается в том, что запрос PATCH использует меньшую полосу пропускания по сравнению с запросом PUT, поскольку отправляется только часть данных, а не весь объект. Поэтому просто используйте запрос PATCH для обновления определенных записей, таких как (1-3 записи), в то время как запрос PUT для обновления большего количества данных. Вот и все, не думайте слишком много и не беспокойтесь об этом слишком сильно.