Czy powinienem używać PATCH lub PUT w moim interfejsie API REST?


274

Chcę zaprojektować mój punkt końcowy odpoczynku przy użyciu odpowiedniej metody dla następującego scenariusza.

Jest grupa. Każda grupa ma status. Grupa może być aktywowana lub dezaktywowana przez administratora.

Czy powinienem zaprojektować mój punkt końcowy jako

PUT /groups/api/v1/groups/{group id}/status/activate

LUB

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}

1
Oba są w porządku. Ale spójrz na RFC formatu JSON PATCH ( tools.ietf.org/html/rfc6902 ). PATCH oczekuje, że dostanie jakiś dokument różnicy / łaty dla ładunku (a surowy JSON nie jest jednym z nich).
Jørn Wildt

1
@ JørnWildt nie, PUT byłby okropnym wyborem. Co tam umieszczasz PATCH to jedyna sensowna opcja. Cóż, w tym przypadku możesz użyć formatu PATCH przedstawionego w pytaniu i po prostu użyć metody PUT; przykład PUT jest po prostu zły.
thecoshman

3
Nie ma nic złego w ujawnianiu jednej lub więcej właściwości jako samodzielnych zasobów, które klient może UZYSKAĆ ​​i zmodyfikować za pomocą PUT. Ale tak, adres URL powinien wtedy mieć postać / groups / api / v1 / groups / {group id} / status, do którego możesz przypisać „aktywne” lub „nieaktywne” lub GET, aby odczytać aktualny stan.
Jørn Wildt

3
Oto dobre wyjaśnienie, w jaki sposób należy używać PATCH: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat

4
activate” nie jest odpowiednią konstrukcją RESTful. Prawdopodobnie próbujesz zaktualizować statusopcję „aktywne” lub „nieaktywne”. w takim przypadku możesz PATCHOWAĆ .../statusz ciągiem „aktywnym” lub „dezaktywującym” w ciele. Lub jeśli próbujesz zaktualizować wartość logiczną o status.active, możesz PATCHOWAĆ .../status/activez wartością logiczną w ciele
Augie Gardner

Odpowiedzi:


328

PATCHMetoda jest poprawny wybór tu jesteś aktualizowania istniejącego zasobu - identyfikator grupy. PUTpowinien być używany tylko wtedy, gdy wymieniasz zasób w całości.

Dalsze informacje na temat częściowej modyfikacji zasobów są dostępne w RFC 5789 . W szczególności PUTmetodę opisano w następujący sposób:

Kilka aplikacji rozszerzających protokół przesyłania hipertekstu (HTTP) wymaga funkcji częściowej modyfikacji zasobów. Istniejąca metoda HTTP PUT pozwala jedynie na całkowitą zamianę dokumentu. Ta propozycja dodaje nową metodę HTTP, PATCH, w celu zmodyfikowania istniejącego zasobu HTTP.


1
Aby być uczciwym, możesz umieścić ciąg „aktywuj” lub „dezaktywuj” do zasobu. Ponieważ (wydaje się) być tylko jedną rzeczą do przełączenia, całkowite zastąpienie go nie jest tak wielkim problemem. I pozwala na (nieznacznie) mniejsze żądanie.
thecoshman

35
Należy zauważyć, że RFC 5789 jest wciąż w fazie składania wniosków i nie został oficjalnie zaakceptowany i obecnie jest oflagowany jako „Irata istnieje”. Ta „najlepsza praktyka” jest szeroko dyskutowana i technicznie PATCH nie jest jeszcze częścią standardu HTTP.
fishpen0

4
Tylko moje 2 centy kilka lat później: możesz uznać sam status za zasób, a jeśli tak, użycie PUT przeciwko / status technicznie zastąpiłoby zasób statusu w tym punkcie końcowym.
Jono Stewart

3
Odważyłbym się kłócić przeciwko dokumentom, nawet jeśli jest to „RFC”. Dokumenty stwierdzają, że należy użyć PATCH, aby zmodyfikować tylko część zasobu, ale pominięto ważną rzecz, którą metoda PATCH jest definiowana jako metoda niebędąca idempotentną. Czemu? Jeśli metoda PUT została stworzona z myślą o aktualizacji / wymianie całego zasobu, to dlaczego nie stworzono metody PATCH jako idempotentnej metody, takiej jak PUT, jeśli jej celem była tylko aktualizacja części zasobu? Dla mnie wygląda to bardziej na różnicę w idempotencji aktualizacji, jak „a = 5” (PUT) i „a = a + 5” (PATCH). Oba mogą zaktualizować cały zasób.
Mladen B.

179

R w REST oznacza zasób

(Co nie jest prawdą, ponieważ oznacza Reprezentację, ale dobrym pomysłem jest zapamiętanie znaczenia zasobów w REST).

Informacje PUT /groups/api/v1/groups/{group id}/status/activate: nie aktualizujesz „aktywuj”. „Aktywacja” nie jest rzeczą, to czasownik. Czasowniki nigdy nie są dobrymi zasobami. Ogólna zasada: jeśli akcja, czasownik, znajduje się w adresie URL, prawdopodobnie nie jest to RESTful .

Co zamiast tego robisz? Albo „dodajesz”, „usuwasz” lub „aktualizujesz” aktywację w grupie, albo jeśli wolisz: manipulujesz zasobem „statusowym” w grupie. Osobiście użyłbym „aktywacji”, ponieważ są one mniej dwuznaczne niż pojęcie „status”: tworzenie statusu jest niejednoznaczne, tworzenie aktywacji nie jest.

  • POST /groups/{group id}/activation Tworzy (lub prosi o utworzenie) aktywacji.
  • PATCH /groups/{group id}/activationAktualizuje niektóre szczegóły istniejącej aktywacji. Ponieważ grupa ma tylko jedną aktywację, wiemy, do jakiego zasobu aktywacyjnego mamy na myśli.
  • PUT /groups/{group id}/activationWstawia lub zastępuje starą aktywację. Ponieważ grupa ma tylko jedną aktywację, wiemy, do jakiego zasobu aktywacyjnego mamy na myśli.
  • DELETE /groups/{group id}/activation Anuluje lub usunie aktywację.

Ten wzór jest przydatny, gdy „aktywacja” grupy ma skutki uboczne, takie jak dokonywanie płatności, wysyłanie wiadomości e-mail i tak dalej. Tylko POST i PATCH mogą mieć takie skutki uboczne. Gdy np. Usunięcie aktywacji wymaga, powiedzmy, powiadomienia użytkowników pocztą, DELETE nie jest właściwym wyborem; w takim przypadku prawdopodobnie chcesz utworzyć zasób dezaktywacji : POST /groups/{group_id}/deactivation.

Dobrym pomysłem jest przestrzeganie tych wytycznych, ponieważ ta standardowa umowa wyraźnie pokazuje klientom, a wszystkie proxy i warstwy między klientem a tobą wiedzą, kiedy można bezpiecznie ponowić próbę, a kiedy nie. Powiedzmy, że klient jest gdzieś z niestabilnym Wi-Fi, a jego użytkownik klika „dezaktywuj”, co powoduje DELETE: Jeśli to się nie powiedzie, klient może po prostu spróbować ponownie, aż otrzyma 404, 200 lub cokolwiek innego, co może obsłużyć. Ale jeśli uruchomi się POST to deactivation, wie, że nie można ponowić: POST implikuje to.
Każdy klient ma teraz umowę, której przestrzeganie ochroni przed wysłaniem 42 e-maili „Twoja grupa została zdezaktywowana”, po prostu dlatego, że jej biblioteka HTTP ponawiała połączenie z backendem.

Aktualizowanie pojedynczego atrybutu: użyj PATCH

PATCH /groups/{group id}

W przypadku, gdy chcesz zaktualizować atrybut. Np. „Status” może być atrybutem w Grupach, które można ustawić. Atrybut taki jak „status” jest często dobrym kandydatem do ograniczenia do białej listy wartości. Przykłady wykorzystują niezdefiniowany schemat JSON:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

Zastępując zasób bez efektów ubocznych użyj PUT.

PUT /groups/{group id}

Jeśli chcesz zastąpić całą Grupę. Nie musi to koniecznie oznaczać, że serwer faktycznie tworzy nową grupę i wyrzuca starą, np. Identyfikatory mogą pozostać takie same. Ale dla klientów to właśnie może oznaczać PUT : klient powinien założyć, że otrzymuje zupełnie nowy przedmiot, w oparciu o odpowiedź serwera.

W przypadku PUTżądania klient powinien zawsze wysyłać cały zasób, mając wszystkie dane potrzebne do utworzenia nowego elementu: zwykle te same dane, których wymagałoby utworzenie POST.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Bardzo ważnym wymogiem jest PUTidempotent: jeśli potrzebujesz efektów ubocznych podczas aktualizacji Grupy (lub zmiany aktywacji), powinieneś użyć PATCH. Jeśli więc aktualizacja spowoduje np. Wysłanie wiadomości e-mail, nie należy jej używać PUT.


3
To było dla mnie bardzo pouczające. „Ten wzorzec jest przydatny, gdy„ aktywacja ”grupy ma skutki uboczne” - Dlaczego ten wzorzec jest użyteczny, szczególnie w odniesieniu do tego, kiedy działania mają skutki uboczne, w przeciwieństwie do początkowych punktów końcowych PO
Abdul

1
@Abdul, wzorzec jest użyteczny z wielu powodów, ale bez skutków ubocznych, powinno być bardzo jasne dla klienta, jakie skutki ma działanie. Gdy powiedzmy, że aplikacja na iOS decyduje się wysłać całą książkę adresową jako „kontakty”, powinno być bardzo jasne, jakie skutki uboczne ma tworzenie, aktualizacja, usuwanie itp. Kontaktu. Na przykład, aby uniknąć masowego wysyłania wszystkich kontaktów.
berkes

1
W RESTfull PUT może także zmieniać tożsamość encji - na przykład identyfikator PrimaryKey, gdzie może to spowodować niepowodzenie równoległego żądania. (na przykład aktualizacja całej encji musi usunąć niektóre wiersze i dodać nowe, a zatem tworzyć nowe encje). PATCH nigdy nie może tego zrobić, pozwalając na nieograniczoną liczbę żądań PATCH bez wpływu na inne „aplikacje”
Piotr Kula,

1
Bardzo pomocna odpowiedź. Dzięki! Dodałbym również komentarz, podobnie jak w odpowiedzi Luke'a, wskazując, że różnica między PUT / PATCH to nie tylko cała / częściowa aktualizacja, ale także idempotencja, która jest inna. To nie był błąd, to była celowa decyzja i myślę, że niewielu ludzi bierze to pod uwagę, decydując się na użycie metody HTTP.
Mladen B.

1
Usługi @richremer, podobnie jak modele, są wewnętrznymi abstrakcjami. Tak jak wymaganie relacji 1-1 między modelami punktów końcowych REST i modeli ORM, a nawet tabelami bazy danych jest słabą abstrakcją, tak samo ujawnianie usług jest słabą abstrakcją. Zewnętrzne, twój interfejs API, musi komunikować modele domen. Sposób ich implementacji wewnętrznej nie ma znaczenia dla interfejsu API. Powinieneś mieć swobodę przejścia z usługi ActivationService do przepływu aktywacyjnego opartego na CQRS, bez konieczności zmiany interfejsu API.
berkes

12

Polecam użycie PATCH, ponieważ twoja grupa zasobów ma wiele właściwości, ale w tym przypadku aktualizujesz tylko pole aktywacji (częściowa modyfikacja)

zgodnie z RFC5789 ( https://tools.ietf.org/html/rfc5789 )

Istniejąca metoda HTTP PUT pozwala jedynie na całkowitą zamianę dokumentu. Ta propozycja dodaje nową metodę HTTP, PATCH, w celu zmodyfikowania istniejącego zasobu HTTP.

Ponadto, bardziej szczegółowo

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ę. 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 modyfikowane istniejące przez zastosowanie
PATCH.

PATCH nie jest bezpieczny ani idempotentny zgodnie z definicją w [RFC2616], Rozdział 9.1.

Klienci muszą wybrać, kiedy użyć PATCH zamiast PUT. Na
przykład, jeśli rozmiar dokumentu poprawki jest większy niż rozmiar
nowych danych zasobów, które byłyby użyte w PUT, wówczas
sensowne może być użycie PUT zamiast PATCH. Porównanie z testem POST jest jeszcze trudniejsze, ponieważ test POST jest używany na wiele różnych sposobów i może
obejmować operacje typu PUT i PATCH, jeśli serwer zdecyduje. Jeśli
operacja nie modyfikuje zasobu identyfikowanego przez identyfikator URI żądania w przewidywalny sposób, należy rozważyć POST zamiast PATCH
lub PUT.

Kod odpowiedzi dla PATCH to

Używany jest kod odpowiedzi 204, ponieważ odpowiedź nie zawiera treści komunikatu (co miałaby odpowiedź z kodem 200). Pamiętaj, że można również użyć innych kodów sukcesu.

patrz również: http: //restcookbook.com/HTTP%20Methods/patch/

Ostrzeżenie: interfejs API implementujący PATCH musi łatać atomowo. MUSI nie być możliwe, aby zasoby były w połowie załatane na żądanie GET.


7

Ponieważ chcesz zaprojektować interfejs API przy użyciu stylu architektonicznego REST, musisz pomyśleć o swoich przypadkach użycia, aby zdecydować, które koncepcje są wystarczająco ważne, aby ujawnić je jako zasoby. Jeśli zdecydujesz się na ujawnienie statusu grupy jako zasobu podrzędnego, możesz podać jej następujący identyfikator URI i zaimplementować obsługę metod GET i PUT:

/groups/api/groups/{group id}/status

Wadą tego podejścia w stosunku do PATCH w zakresie modyfikacji jest to, że nie będziesz w stanie dokonywać zmian w więcej niż jednej właściwości grupy atomowo i transakcyjnie. Jeśli zmiany transakcyjne są ważne, użyj PATCH.

Jeśli zdecydujesz się ujawnić status jako podrzędny zasób grupy, powinien to być link w reprezentacji grupy. Na przykład, jeśli agent pobierze grupę 123 i zaakceptuje XML, treść odpowiedzi może zawierać:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

Hiperlink jest potrzebny do spełnienia hipermediów jako silnika stanu stanu aplikacji stylu architektonicznego REST.


0

Generalnie wolałbym coś nieco prostszego, na przykład activate/ deactivatesub-resource (połączone Linknagłówkiem z rel=service).

POST /groups/api/v1/groups/{group id}/activate

lub

POST /groups/api/v1/groups/{group id}/deactivate

Dla konsumenta ten interfejs jest niesamowicie prosty i działa zgodnie z zasadami REST, nie wprowadzając cię w konceptualizację „aktywacji” jako pojedynczych zasobów.


0

Jedną z możliwych opcji wdrożenia takiego zachowania jest

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

I oczywiście, jeśli ktoś będzie musiał go dezaktywować, PUTbędzie miał Deactivatedstatus w JSON.

W przypadku konieczności masowej aktywacji / dezaktywacji PATCHmoże wejść do gry (nie dla dokładnej grupy, ale dla groupszasobu:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

Ogólnie jest to pomysł, jak sugeruje @Andrew Dobrowolski, ale z niewielkimi zmianami w dokładnej realizacji.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.