UWAGA : Kiedy po raz pierwszy spędziłem czas na czytaniu o REST, idempotencja była mylącym pomysłem, aby spróbować naprawić. Nadal nie zrozumiałem tego poprawnie w mojej oryginalnej odpowiedzi, co pokazały dalsze komentarze (i odpowiedź Jasona Hoetgera ). Przez pewien czas opierałem się aktualizacji tej odpowiedzi, aby uniknąć skutecznego plagiatowania Jasona, ale teraz edytuję ją, ponieważ, no cóż, zostałem o to poproszony (w komentarzach).
Po przeczytaniu mojej odpowiedzi sugeruję, abyście również przeczytali doskonałą odpowiedź Jasona Hoetgera na to pytanie, a ja postaram się poprawić moją odpowiedź bez kradzieży przed Jasonem.
Dlaczego PUT jest idempotentny?
Jak zauważyłeś w swoim cytacie RFC 2616, PUT jest uważany za idempotentny. Kiedy umieszczasz zasób, obowiązują te dwa założenia:
Odwołujesz się do bytu, a nie do kolekcji.
Podawany podmiot jest kompletny ( cały podmiot).
Spójrzmy na jeden z twoich przykładów.
{ "username": "skwee357", "email": "skwee357@domain.com" }
Jeśli prześlesz ten dokument do /users
, jak sugerujesz, możesz odzyskać podmiot taki jak
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
Jeśli chcesz zmodyfikować ten obiekt później, wybierasz pomiędzy PUT i PATCH. PUT może wyglądać następująco:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
Możesz to zrobić za pomocą PATCH. Może to wyglądać tak:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
Od razu zauważysz różnicę między tymi dwoma. PUT zawierał wszystkie parametry tego użytkownika, ale PATCH obejmował tylko ten, który był modyfikowany ( email
).
Podczas korzystania z PUT zakłada się, że wysyłasz kompletną jednostkę i że kompletna jednostka zastępuje każdą istniejącą jednostkę o tym URI. W powyższym przykładzie PUT i PATCH osiągają ten sam cel: oba zmieniają adres e-mail tego użytkownika. Ale PUT zajmuje się tym, zastępując cały byt, podczas gdy PATCH aktualizuje tylko pola, które zostały dostarczone, pozostawiając pozostałe w spokoju.
Ponieważ żądania PUT obejmują cały byt, wielokrotne wysyłanie tego samego żądania powinno zawsze mieć ten sam wynik (przesłane dane to teraz całe dane podmiotu). Dlatego PUT jest idempotentny.
Niepoprawne użycie PUT
Co się stanie, jeśli użyjesz powyższych danych PATCH w żądaniu 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!
}
(Na potrzeby tego pytania zakładam, że serwer nie ma żadnych konkretnych wymaganych pól i pozwoliłoby na to ... nie może tak być w rzeczywistości).
Ponieważ użyliśmy PUT, ale tylko dostarczyliśmy email
, teraz jest to jedyna rzecz w tym podmiocie. Spowodowało to utratę danych.
Ten przykład jest tutaj w celach ilustracyjnych - nigdy tak naprawdę nie rób tego. To żądanie PUT jest technicznie idempotentne, ale to nie znaczy, że nie jest to okropny, zepsuty pomysł.
Jak PATCH może być idempotentny?
W powyższym przykładzie PATCH był idempotentny. Dokonałeś zmiany, ale jeśli dokonasz tej samej zmiany raz za razem, zawsze da to ten sam wynik: zmieniłeś adres e-mail na nową wartość.
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
}
Mój oryginalny przykład, poprawiony dla dokładności
Pierwotnie miałem przykłady, które moim zdaniem wykazywały brak idempotencji, ale były one mylące / nieprawidłowe. Zamierzam zachować przykłady, ale wykorzystam je do zilustrowania innej rzeczy: to, że wiele dokumentów PATCH względem tego samego obiektu, modyfikując różne atrybuty, nie powoduje, że PATCH nie są idempotentne.
Powiedzmy, że w przeszłości dodano użytkownika. To jest stan, od którego zaczynasz.
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Po PATCH masz zmodyfikowany byt:
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"
}
Jeśli następnie wielokrotnie zastosujesz PATCH, nadal będziesz otrzymywać ten sam wynik: wiadomość e-mail została zmieniona na nową wartość. A wchodzi, A wychodzi, dlatego jest to idempotentne.
Godzinę później, po tym jak poszedłeś zrobić kawę i zrobić sobie przerwę, ktoś inny przyszedł z własną PATCHĄ. Wygląda na to, że poczta dokonała pewnych zmian.
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
}
Ponieważ ta PATCH z urzędu pocztowego nie dotyczy samego e-maila, tylko kod pocztowy, jeśli jest wielokrotnie stosowany, otrzyma ten sam wynik: kod pocztowy zostanie ustawiony na nową wartość. A wchodzi, A wychodzi, dlatego też jest to idempotentne.
Następnego dnia postanawiasz ponownie wysłać PATCH.
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"
}
Twoja łatka ma taki sam efekt jak wczoraj: ustawiła adres e-mail. A wszedł, A wyszedł, dlatego też jest idempotentny.
Co popełniłem źle w mojej oryginalnej odpowiedzi
Chcę narysować ważne rozróżnienie (coś popełniłem w mojej pierwotnej odpowiedzi). Wiele serwerów będzie odpowiadać na żądania REST, wysyłając z powrotem nowy stan encji wraz z wprowadzonymi modyfikacjami (jeśli takie istnieją). Kiedy więc otrzymasz tę odpowiedź , różni się ona od tej, którą otrzymałeś wczoraj , ponieważ kod pocztowy nie jest tym, który otrzymałeś ostatnim razem. Jednak twoje zapytanie nie dotyczyło kodu pocztowego, tylko e-maila. Zatem twój dokument PATCH jest wciąż idempotentny - wiadomość e-mail wysłana w PATCH jest teraz adresem e-mail jednostki.
Więc kiedy PATCH nie jest idempotentny?
Aby uzyskać pełne omówienie tego pytania, ponownie odsyłam do odpowiedzi Jasona Hoetgera . Po prostu to zostawię, bo szczerze mówiąc, nie sądzę, żebym mógł odpowiedzieć na tę część lepiej niż on.