Dlaczego metoda PATCH nie jest idempotentna?

48

Zastanawiałem się nad tym.

Załóżmy, że mam userzasób idi namepolami. Jeśli chcę zaktualizować pole, mogę po prostu wysłać PATCH do takiego zasobu

PATCH /users/42
{"name": "john doe"} 

A następnie aplikacja zaktualizuje nazwę użytkownika 42.

Ale dlaczego, jeśli powtórzę tę prośbę, wynik byłby inny?

Zgodnie z RFC 5789

PATCH nie jest bezpieczny ani idempotentny

mattecapu
źródło
co jeśli między wywołaniami ktoś inny poprosi o aktualizację użytkownika 42{"name": "bendjamin franklin"}
gnat
@gnat nie ma podobnego argumentu również dla metody PUT, która jest zamiast tego uważana za idempotentną? (patrz goo.gl/t24ZSJ )
mattecapu
„PUT ma idempotentną semantykę i dlatego może być bezpiecznie używany do aktualizacji bezwzględnych (tj. Wysyłamy cały stan zasobu do serwera), ale nie do aktualizacji względnych (tj. Wysyłamy tylko zmiany do stanu zasobów) , ponieważ byłoby to sprzeczne z jego semantyka ... ”( żądania POST i PUT - czy to tylko konwencja? )
komnata
1
Oczywiście ... Ale można powiedzieć, że PUT nie jest idempotentny, ponieważ między dwoma równymi żądaniami drugi klient może złożyć trzecie żądanie między nimi. Ale ponieważ nie dbamy o poprzednie dane, nie stanowi to problemu. Ten sam argument dotyczy żądań PATCH.
mattecapu
2
Pozwoliłem sobie dodać odniesienie do odpowiedniej specyfikacji, ponieważ uważam, że jest to bardzo istotne w kontekście tego pytania.
Pete

Odpowiedzi:

34

Żądanie PATCH może być idempotentne, ale nie jest to wymagane. Z tego powodu określa się go jako niestosowny.

To, czy PATCH może być idempotentny, zależy od tego, w jaki sposób przekazywane są wymagane zmiany.
Na przykład, jeśli format poprawki ma postać {change: 'Name' from: 'benjamin franklin' to: 'john doe'}, to każde żądanie PATCH po pierwszym będzie miało inny efekt (odpowiedź na błąd) niż pierwsze żądanie.
Innym powodem braku idempotencji może być to, że zastosowanie modyfikacji do czegoś innego niż oryginalny zasób może spowodować, że zasób będzie nieważny. Tak byłoby również w przypadku wielokrotnego zastosowania zmiany.

Bart van Ingen Schenau
źródło
3
Nie rozumiem ostatniego akapitu, czy możesz podać przykład, w jaki sposób „zastosowanie modyfikacji do czegoś innego niż oryginalny zasób może unieważnić zasób” i jak odnosi się to do wielokrotnego zastosowania zmiany do tego samego zasobu?
Robin Green,
3
@RobinGreen: Załóżmy, że zasób jest reprezentowany w formacie XML z wymogiem, że istnieje co najwyżej jeden <name>element. Jeśli PATCH doda <name>element do zasobu, który pierwotnie go nie zawierał, wówczas zastosowanie PATCH dwukrotnie (lub zastosowanie go do zasobu, który już zawiera a <name>) powoduje, że zasób jest nieprawidłowy, ponieważ nagle zawierałby dwa <name>elementy, co jest niedozwolone dla takich zasobów.
Bart van Ingen Schenau
13
Pierwszy podany przez ciebie przykład nie jest odpowiedni IMHO, ponieważ jest idempotentny. Przykład z DELETE, który jest idempotent, pierwsze żądanie: zasób istniał, ale zostało usunięte (zwraca 2xx), drugie żądanie: zasób nie istnieje i nadal nie istnieje po żądaniu, zwraca 4xx. Stan serwera nie zmienił się, dlatego idempotencja. W twoim przykładzie pierwsze żądanie: PATCH od BenFra do JohDoe, nazwa zasobu to BenFra, ale teraz to JohDoe (zwraca 2xx), drugie żądanie: PATCH od BenFra do JohDoe, nazwa zasobu to JohDoe i nadal to JohDoe (zwraca 4xx). Więc to nie pokazuje, że PATCH może być niestosowny.
sp00m
Więcej informacji tutaj: stackoverflow.com/q/4088350/1225328
sp00m
8

Myślę, że jasną odpowiedzią, gdy PATCH nie jest idempotentny, jest ten akapit z RFC 5789:

Są również przypadki, w których formaty łatek nie muszą działać ze znanego punktu bazowego (np. Dodawanie wierszy tekstowych do plików dziennika lub nie kolidujących wierszy do tabel bazy danych), w którym to przypadku taka sama ostrożność w żądaniach klientów nie jest potrzebna .

Ponieważ RFC określa, że ​​łatka zawiera pewne „ogólne zmiany” w zasobie, powinniśmy wyjść poza zwykłą zamianę pola. Jeśli zasób jest przeznaczony dla licznika, łata może zażądać jego przyrostu, co oczywiście nie jest idempotet.

Ivan
źródło
2

PATCHżądania opisują zestaw operacji, które mają być zastosowane do zasobu, jeśli zastosujesz ten sam zestaw operacji dwa razy do tego samego zasobu, wynik może być inny. Jest tak, ponieważ definiowanie operacji zależy od Ciebie. Innymi słowy, musisz zdefiniować reguły łączenia .

Pamiętaj, że PATCHprośba może być wykorzystana do łatania zasobów w wielu różnych formatach, nie tylko w JSON.

Dlatego PATCHżądanie może być idempotentne, jeśli zdefiniujesz reguły scalania jako idempotentne .

Idempotentny przykład:

// Original resource
{
  name: 'Tito',
  age: 32
}

// PATCH request
{
  age: 33
}

// New resource
{
  name: 'Tito',
  age: 33
}

Przykład nie idempotentny:

// Original resource
{
  name: 'Tito',
  age: 32
}

// PATCH request
{
  $increment: 'age'
}

// New resource
{
  name: 'Tito',
  age: 33
}

W drugim przykładzie użyłem składni „Mongo like”, którą zrekompensowałem do zwiększenia atrybutu. Oczywiście nie jest to idempotentne, ponieważ wielokrotne wysyłanie tego samego żądania spowodowałoby za każdym razem inne wyniki.

Teraz możesz się zastanawiać, czy użycie takiej wymyślonej składni jest prawidłowe. Zgodnie ze standardami jest to:

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, jak należy obecnie zmodyfikować zasób znajdujący się na serwerze źródłowym, aby utworzyć nową wersję.

Być może zastanawiasz się również, czy korzystanie z żądań w ten sposób jest spokojnePATCH , a wiele osób uważa, że ​​tak nie jest, oto dobra odpowiedź z dużą ilością komentarzy na ten temat.

Jbm
źródło