ПРИМЕЧАНИЕ : Когда я впервые потратил время на чтение о 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, теперь является адресом электронной почты объекта.
Так когда же патч не идемпотентен?
Для полного рассмотрения этого вопроса я снова отсылаю вас к ответу Джейсона Хетгера . Я просто собираюсь на этом остановиться, потому что, честно говоря, не думаю, что смогу ответить на этот вопрос лучше, чем он уже сказал.