Przede wszystkim niektóre definicje:
PUT zdefiniowano w rozdziale 9.6 RFC 2616 :
Metoda PUT żąda, aby zamknięta jednostka była przechowywana pod dostarczonym URI żądania. Jeśli identyfikator URI żądania odnosi się do już istniejącego zasobu, dołączony byt MUSI być uważany za zmodyfikowaną wersję tego, który znajduje się na serwerze źródłowym . Jeśli identyfikator URI żądania nie wskazuje na istniejący zasób, a ten identyfikator URI może zostać zdefiniowany jako nowy zasób przez żądającego agenta użytkownika, serwer źródłowy może utworzyć zasób z tym identyfikatorem URI.
PATCH jest zdefiniowany w RFC 5789 :
Metoda PATCH żąda zastosowania zestawu zmian opisanych w encji żądania do zasobu określonego przez identyfikator URI żądania.
Również zgodnie z RFC 2616 sekcja 9.1.2 PUT jest idempotentny, a PATCH nie.
Spójrzmy teraz na prawdziwy przykład. Kiedy wykonam test POST /users
z danymi, {username: 'skwee357', email: '[email protected]'}
a serwer będzie w stanie utworzyć zasób, odpowie 201 i lokalizacją zasobu (załóżmy /users/1
), a każde następne wywołanie GET /users/1
powróci {id: 1, username: 'skwee357', email: '[email protected]'}
.
Powiedzmy teraz, że chcę zmodyfikować swój e-mail. Modyfikacja wiadomości e-mail jest uważana za „zestaw zmian”, dlatego powinienem ŁATWAĆ za /users/1
pomocą „ łatki dokumentu ”. W moim przypadku będzie to dokument json: {email: '[email protected]'}
. Serwer zwraca 200 (przy założeniu, że zezwolenie jest w porządku). To prowadzi mnie do pierwszego pytania:
- PATCH NIE jest idempotentny. Tak było w RFC 2616 i RFC 5789. Jeśli jednak wydam to samo żądanie PATCH (z nowym e-mailem), otrzymam ten sam stan zasobów (z modyfikacją mojego e-maila do żądanej wartości). Dlaczego PATCH nie jest zatem idempotentny?
PATCH to stosunkowo nowy czasownik (RFC wprowadzony w marcu 2010 r.), Który rozwiązuje problem „łatania” lub modyfikowania zestawu pól. Przed wprowadzeniem PATCH wszyscy używali PUT do aktualizacji zasobów. Ale po wprowadzeniu PATCH, nie jestem pewien, do czego służy PUT. To prowadzi mnie do drugiego (i głównego) pytania:
- Jaka jest prawdziwa różnica między PUT a PATCH? Czytałem gdzieś, że PUT może być użyty do zastąpienia całego bytu pod określonym zasobem, więc należy wysłać pełny byt (zamiast zestawu atrybutów jak w PATCH). Jakie jest praktyczne zastosowanie takiego przypadku? Kiedy chcesz zastąpić / zastąpić encję w określonym identyfikatorze URI zasobu i dlaczego taka operacja nie jest uważana za aktualizację / łatanie encji? Jedynym praktycznym przypadkiem, jaki widzę dla PUT, jest wydanie PUT dla kolekcji, tj.
/users
Zastąpienie całej kolekcji. Wydawanie PUT dla konkretnego obiektu nie ma sensu po wprowadzeniu PATCH. Czy się mylę?
Odpowiedzi:
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.
Jeśli prześlesz ten dokument do
/users
, jak sugerujesz, możesz odzyskać podmiot taki jakJeśli chcesz zmodyfikować ten obiekt później, wybierasz pomiędzy PUT i PATCH. PUT może wyglądać następująco:
Możesz to zrobić za pomocą PATCH. Może to wyglądać tak:
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?
(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ść.
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.
Po PATCH masz zmodyfikowany byt:
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.
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.
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.
źródło
GET /users/1
zrobiłbyś to przed zaktualizowaniem przez pocztę kodu pocztowego, a następnie ponownie wysłałeś to samoGET /users/1
żądanie po aktualizacji urzędu pocztowego, uzyskałbyś dwie różne odpowiedzi (różne kody pocztowe). Wchodzi to samo „A” (żądanie GET), ale otrzymujesz różne wyniki. Jednak GET wciąż jest idempotentny.Chociaż znakomita odpowiedź Dana Lowe'a bardzo dokładnie odpowiedziała na pytanie OP dotyczące różnicy między PUT a PATCH, odpowiedź na pytanie, dlaczego PATCH nie jest idempotentna, nie jest całkiem poprawna.
Aby pokazać, dlaczego PATCH nie jest idempotentny, warto zacząć od definicji idempotencji (z Wikipedii ):
W bardziej przystępnym języku idempotentną PATCH można zdefiniować jako: Po PATCHOWANIU zasobu z dokumentem poprawki wszystkie kolejne wywołania PATCH do tego samego zasobu z tym samym dokumentem poprawki nie zmienią zasobu.
I odwrotnie, operacją niebędącą idempotentną jest operacja, w której f (f (x))! = F (x), która dla PATCH może być określona jako: Po PATCHOWANIE zasobu z dokumentem poprawki, kolejne PATCH wywołuje ten sam zasób z ten sam dokument łata zrobić zmianę zasobu.
Aby zilustrować nie idempotentną PATCH, załóżmy, że istnieje zasób / users i załóżmy, że wywołanie
GET /users
zwraca listę użytkowników, obecnie:Zamiast PATCHing / users / {id}, jak w przykładzie PO, załóżmy, że serwer pozwala PATCHing / users. Wydajmy tę prośbę PATCH:
Nasz dokument łatki instruuje serwer, aby dodał nowego użytkownika o nazwie
newuser
do listy użytkowników. Po pierwszym wywołaniuGET /users
zwróci:Co się stanie, jeśli wydamy dokładnie takie samo żądanie PATCH jak powyżej? (Na potrzeby tego przykładu załóżmy, że zasób / users pozwala na duplikowanie nazw użytkowników.) „Op” to „add”, więc nowy użytkownik zostaje dodany do listy, a następnie
GET /users
zwraca:Zasób / users zmienił się ponownie , mimo że wydaliśmy dokładnie taką samą PATCH dla dokładnie tego samego punktu końcowego. Jeśli nasza PATCH to f (x), f (f (x)) nie jest tym samym co f (x), a zatem ta konkretna PATCH nie jest idempotentna .
Chociaż PATCH nie ma gwarancji, że jest idempotentny, w specyfikacji PATCH nic nie stoi na przeszkodzie, aby wszystkie operacje PATCH na danym serwerze były idempotentne. RFC 5789 przewiduje nawet zalety idempotentnych żądań PATCH:
W przykładzie Dana jego operacja PATCH jest w rzeczywistości idempotentna. W tym przykładzie jednostka / users / 1 zmieniła się między naszymi żądaniami PATCH, ale nie z powodu naszych żądań PATCH; to faktycznie inny dokument poprawki Urzędu Pocztowego spowodował zmianę kodu pocztowego. Różne PATCH Poczty to inna operacja; jeśli nasza PATCH ma wartość f (x), PATCH POCZTY to g (x). Idempotencja stwierdza
f(f(f(x))) = f(x)
, ale nie daje żadnych gwarancjif(g(f(x)))
.źródło
/users
, spowodowałoby to, że PUT również nie byłby idempotentny. Wszystko sprowadza się do tego, jak serwer jest zaprojektowany do obsługi żądań./users
), Każde żądanie PUT powinno zastąpić zawartość tej kolekcji. Tak więc PUT/users
powinien spodziewać się kolekcji użytkowników i usunąć wszystkie inne. To jest idempotentne. Jest mało prawdopodobne, że zrobiłbyś coś takiego na punkcie końcowym / users. Ale coś takiego/users/1/emails
może być kolekcją i może być całkowicie poprawne, aby umożliwić zastąpienie całej kolekcji nową.op
akcją, która wyzwala określoną logikę po stronie serwera. Wymagałoby to, aby serwer i klient zdawali sobie sprawę z określonych wartości, które należy podać, abyop
pole wyzwoliło przepływy pracy po stronie serwera. W prostszych scenariuszach REST ten rodzajop
funkcjonalności stanowi złą praktykę i prawdopodobnie powinien być obsługiwany bezpośrednio przez czasowniki HTTP.Byłem także ciekawy tego i znalazłem kilka interesujących artykułów. Mogę nie odpowiedzieć na twoje pytanie w pełnym zakresie, ale to przynajmniej zawiera więcej informacji.
http://restful-api-design.readthedocs.org/en/latest/methods.html
Biorąc to pod uwagę, wówczas PUT powinien wysłać cały obiekt. Na przykład,
To skutecznie zaktualizuje wiadomość e-mail. Powodem, dla którego PUT może nie być zbyt skuteczny, jest to, że modyfikowanie tylko jednego pola wraz z nazwą użytkownika jest bezużyteczne. Następny przykład pokazuje różnicę.
Teraz, jeśli PUT został zaprojektowany zgodnie ze specyfikacją, PUT ustawiłby nazwę użytkownika na null i otrzymalibyśmy następujące.
Korzystając z PATCH, aktualizujesz tylko określone pole, a resztę pozostawiasz w spokoju, tak jak w przykładzie.
Poniższe spojrzenie na PATCH jest trochę inne niż nigdy wcześniej.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Mniej więcej traktujesz PATCH jako sposób na aktualizację pola. Zamiast wysyłać częściowy obiekt, przesyłasz operację. tj. Zamień e-mail na wartość.
Artykuł kończy się na tym.
Nie wiem, czy szczególnie zgadzam się z tym artykułem, jak zauważa wielu komentatorów. Przesłanie częściowej reprezentacji może być z łatwością opisem zmian.
Dla mnie mieszam się z używaniem PATCH. W większości przypadków PUT będę traktować jako PATCH, ponieważ jedyną prawdziwą różnicą, jaką zauważyłem do tej pory, jest to, że PUT „powinien” ustawić brakujące wartości na zero. Może nie jest to „najbardziej poprawny” sposób, ale kodowanie powodzenia jest idealne.
źródło
Różnica między PUT a PATCH polega na tym, że:
PATCH wymaga trochę „języka poprawek”, aby powiedzieć serwerowi, jak zmodyfikować zasób. Dzwoniący i serwer muszą zdefiniować niektóre „operacje”, takie jak „dodaj”, „zamień”, „usuń”. Na przykład:
Zamiast używania wyraźnych pól „operacji”, język łatek może uczynić go domyślnym poprzez zdefiniowanie konwencji takich jak:
w treści żądania PATCH:
Zgodnie z powyższą konwencją PATCH w tym przykładzie może przyjąć następującą postać:
Który wygląda na bardziej zwięzły i przyjazny dla użytkownika. Ale użytkownicy muszą być świadomi podstawowej konwencji.
Dzięki operacjom, o których wspomniałem powyżej, PATCH jest wciąż idempotentny. Ale jeśli zdefiniujesz operacje takie jak: „inkrement” lub „append”, łatwo zobaczysz, że nie będzie to już idempotentne.
źródło
TLDR - wersja głupia
PUT => Ustaw wszystkie nowe atrybuty dla istniejącego zasobu.
PATCH => Częściowo zaktualizuj istniejący zasób (nie wszystkie atrybuty są wymagane).
źródło
Pozwolę sobie zacytować i skomentować bardziej szczegółowo sekcję 4.2.2 RFC 7231 , cytowaną już we wcześniejszych komentarzach:
Czym więc powinno być „to samo” po wielokrotnym żądaniu idempotentnej metody? Nie stan serwera ani odpowiedź serwera, ale zamierzony efekt . W szczególności metoda powinna być idempotentna „z punktu widzenia klienta”. Teraz uważam, że ten punkt widzenia pokazuje, że ostatni przykład w odpowiedzi Dana Lowe'a , którego nie chcę tutaj plagiatować, rzeczywiście pokazuje, że żądanie PATCH może być nie idempotentne (w bardziej naturalny sposób niż przykład w Odpowiedź Jasona Hoetgera ).
Rzeczywiście, uczyńmy ten przykład nieco bardziej precyzyjnym, podając jednoznaczny możliwy cel dla pierwszego klienta. Powiedzmy, że ten klient przegląda listę użytkowników projektu, aby sprawdzić ich e - maile i kody pocztowe. Zaczyna od użytkownika 1 i zauważa, że kod pocztowy jest poprawny, ale adres e-mail jest nieprawidłowy. Postanawia to naprawić za pomocą żądania PATCH, które jest w pełni uzasadnione i wysyła tylko
ponieważ jest to jedyna korekta. Teraz żądanie nie powiedzie się z powodu problemów z siecią i zostanie ponownie przesłane automatycznie kilka godzin później. W międzyczasie inny klient (błędnie) zmodyfikował zip użytkownika 1. Następnie, wysłanie tego samego żądania PATCH po raz drugi nie osiąga zamierzonego efektu klienta, ponieważ kończy się to niepoprawnym zipem. Dlatego metoda nie jest idempotentna w rozumieniu RFC.
Jeśli zamiast tego klient użyje żądania PUT do skorygowania wiadomości e-mail, wysyłając do serwera wszystkie właściwości użytkownika 1 wraz z wiadomością e-mail, jego zamierzony efekt zostanie osiągnięty, nawet jeśli żądanie musi zostać ponownie wysłane później, a użytkownik 1 został zmodyfikowany w międzyczasie --- ponieważ drugie żądanie PUT zastąpi wszystkie zmiany od pierwszego żądania.
źródło
Moim skromnym zdaniem idempotencja oznacza:
Wysyłam definicję współzawodnictwa zasobów, więc - wynikowy stan zasobów jest dokładnie taki, jak zdefiniowano w parametrach PUT. Za każdym razem, gdy aktualizuję zasób z tymi samymi parametrami PUT - wynikowy stan jest dokładnie taki sam.
Wysłałem tylko część definicji zasobu, więc może się zdarzyć, że w międzyczasie inni użytkownicy będą aktualizować INNE parametry tego zasobu. W konsekwencji - kolejne łaty o tych samych parametrach i ich wartościach mogą mieć inny stan zasobów. Na przykład:
Załóżmy obiekt zdefiniowany w następujący sposób:
SAMOCHÓD: - kolor: czarny, - typ: sedan, - miejsca: 5
Poprawiam to za pomocą:
{kolor czerwony'}
Powstały obiekt to:
SAMOCHÓD: - kolor: czerwony, - typ: sedan, - miejsca: 5
Następnie niektórzy użytkownicy łatają ten samochód:
{type: „hatchback”}
więc wynikowy obiekt to:
SAMOCHÓD: - kolor: czerwony, - typ: hatchback, - miejsca: 5
Teraz, jeśli ponownie załatam ten obiekt za pomocą:
{kolor czerwony'}
wynikowy obiekt to:
SAMOCHÓD: - kolor: czerwony, - typ: hatchback, - miejsca: 5
Co różni się od tego, co mam wcześniej!
Dlatego PATCH nie jest idempotentny, a PUT jest idempotentny.
źródło
Kończąc dyskusję na temat idempotencji, należy zauważyć, że można zdefiniować idempotencję w kontekście REST na dwa sposoby. Najpierw sformalizujmy kilka rzeczy:
Zasób jest funkcja z jego codomain jest klasa ciągów. Innymi słowy, zasób jest podzbiorem
String × Any
, w którym wszystkie klucze są unikalne. Nazwijmy klasę zasobówRes
.Operacja REST na zasobach jest funkcją
f(x: Res, y: Res): Res
. Dwa przykłady operacji REST to:PUT(x: Res, y: Res): Res = x
, iPATCH(x: Res, y: Res): Res
, który działa jakPATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.(Definicja ta jest specjalnie zaprojektowany, aby spierać
PUT
iPOST
, i na przykład nie ma sensu naGET
iPOST
, jako że nie dba o wytrwałość).Teraz, ustalając
x: Res
(informacyjnie, używając curry),PUT(x: Res)
iPATCH(x: Res)
są jednoznaczne funkcje typuRes → Res
.Funkcja
g: Res → Res
jest nazywane globalnie idempotent , kiedyg ○ g == g
, czyli dla każdegoy: Res
,g(g(y)) = g(y)
.Niech
x: Res
zasób ik = x.keys
. Funkcjag = f(x)
nazywana jest lewą idempotentną , gdy dla każdegoy: Res
mamyg(g(y))|ₖ == g(y)|ₖ
. Zasadniczo oznacza to, że wynik powinien być taki sam, jeśli spojrzymy na zastosowane klucze.Nie
PATCH(x)
jest więc globalnie idempotentny, ale pozostaje idempotentny. A lewa idempotencja jest tutaj sprawą, która się liczy: jeśli załatamy kilka kluczy zasobu, chcemy, aby klucze były takie same, jeśli załatamy go ponownie, i nie dbamy o resztę zasobu.A kiedy RFC mówi o tym, że PATCH nie jest idempotentny, mówi o globalnej idempotencji. Cóż, dobrze, że nie jest globalnie idempotentny, w przeciwnym razie byłaby to zepsuta operacja.
Teraz odpowiedź Jasona Hoetgera próbuje wykazać, że PATCH nie jest nawet idempotentny, ale łamie zbyt wiele rzeczy, aby to zrobić:
t: Set<T> → Map<T, Boolean>
zdefiniowane za pomocąx in A iff t(A)(x) == True
. Zgodnie z tą definicją łatanie pozostaje idempotentne.{id: 1, email: "[email protected]"}
musi się zgadzać{email: "[email protected]"}
, w przeciwnym razie program jest zawsze uszkodzony, a PATCH nie może łata). Jeśli identyfikator zostanie wygenerowany przed porównaniem z zestawem, program ponownie zostanie uszkodzony.Można podać przykłady niestosowania PUT z łamaniem połowy rzeczy, które są zepsute w tym przykładzie:
PUT /user/12 {email: "[email protected]"}
wyniki{email: "...", version: 1}
za pierwszym razem i{email: "...", version: 2}
za drugim razem.Wszystkie powyższe przykłady są naturalnymi przykładami, które można spotkać.
Moja ostatnia uwaga jest taka, że PATCH nie powinien być globalnie idempotentny , w przeciwnym razie nie da pożądanego efektu. Chcesz zmienić adres e-mail użytkownika bez dotykania reszty informacji i nie chcesz zastępować zmian wprowadzonych przez inną stronę uzyskującą dostęp do tego samego zasobu.
źródło
Jedną dodatkową informacją, którą chcę tylko dodać, jest to, że żądanie PATCH zużywa mniejszą przepustowość w porównaniu do żądania PUT, ponieważ tylko część danych nie jest wysyłana jako całość. Więc po prostu użyj żądania PATCH do aktualizacji określonych rekordów, takich jak (1-3 rekordy), podczas gdy żądanie PUT do aktualizacji większej ilości danych. To tyle, nie myśl za dużo i nie przejmuj się tym zbytnio.
źródło