Zastosowanie metod PUT vs PATCH w rzeczywistych scenariuszach interfejsu API REST

681

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 /usersz 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/1powró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/1pomocą „ ł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. /usersZastąpienie całej kolekcji. Wydawanie PUT dla konkretnego obiektu nie ma sensu po wprowadzeniu PATCH. Czy się mylę?
Dmitrij Kudryavtsev
źródło
1
a) jest to RFC 2616, a nie 2612. b) RFC 2616 został przestarzały, aktualna specyfikacja PUT znajduje się w greenbytes.de/tech/webdav/rfc7231.html#PUT , c) nie otrzymuję twojego pytania; czy nie jest całkiem oczywiste, że PUT może być użyty do zastąpienia dowolnego zasobu, nie tylko kolekcji, d) przed wprowadzeniem PATCH, ludzie zwykle używali POST, e) wreszcie, tak, konkretne żądanie PATCH (w zależności od formatu łatki) może być idempotentny; po prostu tak nie jest.
Julian Reschke,
jeśli to pomaga, napisałem artykuł na temat PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
odpowiednik 8
5
Prosty: POST tworzy element w kolekcji. PUT zastępuje element. PATCH modyfikuje element. Podczas testu POST adres URL nowego elementu jest obliczany i zwracany w odpowiedzi, natomiast PUT i PATCH wymagają adresu URL w żądaniu. Dobrze?
Tom Russell,
Ten post może być przydatny: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Odpowiedzi:

943

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:

  1. Odwołujesz się do bytu, a nie do kolekcji.

  2. Podawany podmiot jest kompletny ( cały podmiot).

Spójrzmy na jeden z twoich przykładów.

{ "username": "skwee357", "email": "[email protected]" }

Jeśli prześlesz ten dokument do /users, jak sugerujesz, możesz odzyskać podmiot taki jak

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

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": "[email protected]"       // new email address
}

Możesz to zrobić za pomocą PATCH. Może to wyglądać tak:

PATCH /users/1
{
    "email": "[email protected]"       // 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": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // 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": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // 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": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Po PATCH masz zmodyfikowany byt:

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // 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": "[email protected]",  // 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": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "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.

Dan Lowe
źródło
2
To zdanie nie jest całkiem poprawne: „Ale jest idempotentne: za każdym razem, gdy A wchodzi, B zawsze wychodzi”. Na przykład, jeśli GET /users/1zrobiłbyś to przed zaktualizowaniem przez pocztę kodu pocztowego, a następnie ponownie wysłałeś to samo GET /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.
Jason Hoetger,
@JasonHoetger GET jest bezpieczny (zakłada się, że nie powoduje zmian), ale nie zawsze jest idempotentny. Jest różnica. Patrz RFC 2616 sec. 9.1 .
Dan Lowe,
1
@DanLowe: GET zdecydowanie gwarantuje idempotent. Mówi dokładnie, że w sekcji 9.1.2 RFC 2616 oraz w zaktualizowanej specyfikacji, RFC 7231 sekcja 4.2.2 , że „Spośród metod żądania zdefiniowanych w tej specyfikacji PUT, DELETE i bezpieczne metody żądania są idempotentne”. Idempotencja nie oznacza po prostu „otrzymujesz tę samą odpowiedź za każdym razem, gdy wysyłasz tę samą prośbę”. 7231 4.2.2 mówi dalej: „Powtórzenie żądania będzie miało taki sam zamierzony skutek, nawet jeśli pierwotne żądanie się powiedzie, chociaż odpowiedź może się różnić.
Jason Hoetger
1
@JasonHoetger Przyznaję to, ale nie rozumiem, co to ma wspólnego z tą odpowiedzią, która omawiała PUT i PATCH, a nawet nie wspomina GET ...
Dan Lowe
1
Ach, komentarz @JasonHoetger wyjaśnił: tylko stany wynikowe, a nie odpowiedzi wielu żądań metody idempotentnej muszą być identyczne.
Tom Russell
329

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 ):

Termin idempotent jest używany bardziej kompleksowo do opisania operacji, która przyniesie takie same wyniki, jeśli zostanie wykonana raz lub wiele razy [...] Funkcja idempotentna to taka, która ma właściwość f (f (x)) = f (x) dla dowolna wartość x.

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 /userszwraca listę użytkowników, obecnie:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]

Zamiast PATCHing / users / {id}, jak w przykładzie PO, załóżmy, że serwer pozwala PATCHing / users. Wydajmy tę prośbę PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]

Nasz dokument łatki instruuje serwer, aby dodał nowego użytkownika o nazwie newuserdo listy użytkowników. Po pierwszym wywołaniu GET /userszwróci:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" }]

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 /userszwraca:

[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
 { "id": 2, "username": "newuser", "email": "[email protected]" },
 { "id": 3, "username": "newuser", "email": "[email protected]" }]

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:

Żądanie PATCH może być wydane w taki sposób, aby było idempotentne, co pomaga również zapobiegać złym wynikom kolizji między dwoma żądaniami PATCH dla tego samego zasobu w podobnych ramach czasowych.

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 gwarancji f(g(f(x))).

Jason Hoetger
źródło
11
Zakładając, że serwer zezwala również na wystawienie PUT w /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ń.
Uzair Sajid
13
Tak więc moglibyśmy zbudować API tylko za pomocą operacji PATCH. Co zatem staje się zasadą REST używania HTTP VERBS do wykonywania działań CRUD na zasobach? Czyż nie panowie tutaj nadmiernie komplikujemy granice PATCH?
bohr
6
Jeśli PUT jest zaimplementowany w kolekcji (np. /users), Każde żądanie PUT powinno zastąpić zawartość tej kolekcji. Tak więc PUT /userspowinien 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/emailsmoże być kolekcją i może być całkowicie poprawne, aby umożliwić zastąpienie całej kolekcji nową.
Vectorjohn,
5
Chociaż ta odpowiedź stanowi doskonały przykład idempotencji, uważam, że może to zmącić wody w typowych scenariuszach REST. W takim przypadku masz żądanie PATCH z dodatkową opakcją, 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ć, aby oppole wyzwoliło przepływy pracy po stronie serwera. W prostszych scenariuszach REST ten rodzaj opfunkcjonalności stanowi złą praktykę i prawdopodobnie powinien być obsługiwany bezpośrednio przez czasowniki HTTP.
ivandov
7
Nigdy nie rozważałbym wydania PATCH, tylko POST i DELETE przeciwko kolekcji. Czy to naprawdę zrobiono? Czy PATCH można zatem uznać za idempotentny we wszystkich praktycznych celach?
Tom Russell
72

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

HTTP RFC określa, że ​​PUT musi przyjąć pełną nową reprezentację zasobu jako encję żądania. Oznacza to, że jeśli na przykład podano tylko niektóre atrybuty, należy je usunąć (tzn. Ustawić na null).

Biorąc to pod uwagę, wówczas PUT powinien wysłać cały obiekt. Na przykład,

/users/1
PUT {id: 1, username: 'skwee357', email: '[email protected]'}

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ę.

/users/1
PUT {id: 1, email: '[email protected]'}

Teraz, jeśli PUT został zaprojektowany zgodnie ze specyfikacją, PUT ustawiłby nazwę użytkownika na null i otrzymalibyśmy następujące.

{id: 1, username: null, email: '[email protected]'}

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/

Różnica między żądaniami PUT i PATCH znajduje odzwierciedlenie w sposobie, w jaki serwer przetwarza encję zamkniętą w celu zmodyfikowania zasobu identyfikowanego przez identyfikator URI żądania. W żądaniu PUT załączony obiekt jest uważany za zmodyfikowaną wersję zasobu przechowywanego na serwerze źródłowym, a klient żąda zastąpienia zapisanej wersji. Jednak w PATCH dołączona jednostka zawiera zestaw instrukcji opisujących, w jaki sposób zasób obecnie przebywający na serwerze źródłowym powinien zostać zmodyfikowany, aby utworzyć nową wersję. Metoda PATCH wpływa na zasób zidentyfikowany przez identyfikator URI żądania, i MOŻE mieć również skutki uboczne dla innych zasobów; tj. nowe zasoby mogą być tworzone lub istniejące mogą być modyfikowane przez zastosowanie PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "[email protected]" }
]

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.

Warto wspomnieć, że PATCH nie jest tak naprawdę zaprojektowany dla prawdziwie REST API, ponieważ rozprawa Fieldinga nie określa żadnego sposobu częściowej modyfikacji zasobów. Ale sam Roy Fielding powiedział, że PATCH jest czymś [stworzonym] dla początkowej propozycji HTTP / 1.1, ponieważ częściowe PUT nigdy nie jest RESTful. Na pewno nie przenosisz pełnej reprezentacji, ale REST i tak nie wymaga kompletności reprezentacji.

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.

Kalel Wade
źródło
7
Warto dodać: w artykule Williama Duranda (i rfc 6902) są przykłady, w których „op” to „dodaj”. To oczywiście nie jest idempotentne.
Johannes Brodwall
2
Możesz też ułatwić i zamiast tego użyć łatki scalającej RFC 7396 i unikać budowania łatki JSON.
Piotr Kula
w przypadku tabel nosql ważne są różnice między łatką a putem, ponieważ nosql nie ma kolumn
stackdave
18

Różnica między PUT a PATCH polega na tym, że:

  1. PUT musi być idempotentny. Aby to osiągnąć, musisz umieścić cały pełny zasób w treści żądania.
  2. PATCH może nie być idempotentny. Co oznacza, że ​​może być również idempotentny w niektórych przypadkach, takich jak opisywane przypadki.

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:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "[email protected]"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "state": "NY",
  "address": "123 main street",
}

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:

  1. Istnienie pola oznacza „zamień” lub „dodaj” to pole.
  2. Jeśli wartość pola jest równa null, oznacza to usunięcie tego pola.

Zgodnie z powyższą konwencją PATCH w tym przykładzie może przyjąć następującą postać:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "[email protected]",
  "zip":
}

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.

Bin Ni
źródło
7

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).

Bijan
źródło
3

Pozwolę sobie zacytować i skomentować bardziej szczegółowo sekcję 4.2.2 RFC 7231 , cytowaną już we wcześniejszych komentarzach:

Metodę żądania uważa się za „idempotentną”, jeśli zamierzony efekt na serwerze wielu identycznych żądań z tą metodą jest taki sam jak efekt dla pojedynczego takiego żądania. Spośród metod żądania zdefiniowanych w tej specyfikacji PUT, DELETE i bezpieczne metody żądania są idempotentne.

(...)

Rozróżnia się metody idempotentne, ponieważ żądanie można powtórzyć automatycznie, jeśli wystąpi błąd komunikacji, zanim klient będzie mógł odczytać odpowiedź serwera. Na przykład, jeśli klient wysyła żądanie PUT, a podstawowe połączenie zostaje zamknięte przed otrzymaniem jakiejkolwiek odpowiedzi, wówczas klient może ustanowić nowe połączenie i ponowić żądanie idempotentne. Wie, że powtórzenie żądania będzie miało taki sam zamierzony skutek, nawet jeśli pierwotne żądanie się powiedzie, chociaż odpowiedź może się różnić.

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

PATCH /users/1
{"email": "[email protected]"}

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.

Rolvernew
źródło
2

Moim skromnym zdaniem idempotencja oznacza:

  • POŁOŻYĆ:

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.

  • ŁATA:

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.

Zbigniew Szczęsny
źródło
1

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ów Res.

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, i
  • PATCH(x: Res, y: Res): Res, który działa jak PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Definicja ta jest specjalnie zaprojektowany, aby spierać PUTi POST, i na przykład nie ma sensu na GETi POST, jako że nie dba o wytrwałość).

Teraz, ustalając x: Res(informacyjnie, używając curry), PUT(x: Res)i PATCH(x: Res)są jednoznaczne funkcje typu Res → Res.

  1. Funkcja g: Res → Resjest nazywane globalnie idempotent , kiedy g ○ g == g, czyli dla każdego y: Res, g(g(y)) = g(y).

  2. Niech x: Reszasób i k = x.keys. Funkcja g = f(x)nazywana jest lewą idempotentną , gdy dla każdego y: Resmamy g(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ć:

  • Po pierwsze, PATCH jest używany na zestawie, chociaż PATCH jest zdefiniowany do pracy na mapach / słownikach / obiektach klucz-wartość.
  • Jeśli ktoś naprawdę chce zastosować PATCH do zestawów, istnieje naturalne tłumaczenie, którego należy użyć:, t: Set<T> → Map<T, Boolean>zdefiniowane za pomocą x in A iff t(A)(x) == True. Zgodnie z tą definicją łatanie pozostaje idempotentne.
  • W przykładzie to tłumaczenie nie zostało użyte, zamiast tego PATCH działa jak POST. Po pierwsze, dlaczego identyfikator jest generowany dla obiektu? A kiedy jest generowany? Jeśli obiekt zostanie najpierw porównany z elementami zestawu, a jeśli nie zostanie znaleziony pasujący obiekt, wówczas zostanie wygenerowany identyfikator, następnie program powinien działać inaczej ( {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:

  • Przykładem z wygenerowanymi dodatkowymi funkcjami byłoby przechowywanie wersji. Można prowadzić rejestr liczby zmian na jednym obiekcie. W tym przypadku PUT nie jest idempotentny: PUT /user/12 {email: "[email protected]"}wyniki {email: "...", version: 1}za pierwszym razem i {email: "...", version: 2}za drugim razem.
  • Mając do czynienia z identyfikatorami, można generować nowy identyfikator za każdym razem, gdy obiekt jest aktualizowany, co powoduje nie idempotentne PUT.

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.

Mohammad-Ali A'RÂBI
źródło
-1

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.

Benzoes
źródło