RESTful API. Czy powinienem zwracać obiekt, który został utworzony / zaktualizowany?

36

Projektuję usługę sieci Web RESTful przy użyciu WebApi i zastanawiałem się, jakie odpowiedzi HTTP i treści odpowiedzi zostaną zwrócone podczas aktualizacji / tworzenia obiektów.

Na przykład mogę użyć metody POST, aby wysłać JSON do usługi sieci Web, a następnie utworzyć obiekt. Czy najlepiej jest ustawić status HTTP na utworzony (201) lub ok (200) i po prostu zwrócić komunikat, taki jak „Dodano nowego pracownika”, czy zwrócić obiekt, który został pierwotnie wysłany?

To samo dotyczy metody PUT. Którego stanu HTTP najlepiej użyć i czy muszę zwrócić utworzony obiekt, czy tylko wiadomość? Biorąc pod uwagę fakt, że użytkownik i tak wie, jaki obiekt próbuje utworzyć / zaktualizować.

jakieś pomysły?

Przykład:

Dodaj nowego pracownika:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Zaktualizuj istniejącego pracownika:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Odpowiedzi:

Odpowiedź z obiektem utworzonym / zaktualizowanym

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Odpowiedź z samą wiadomością:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Odpowiedź za pomocą samego kodu statusu:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT
iswinky
źródło
2
Dobre pytanie, ale użycie terminu „najlepsza praktyka” jest rodzajem tabu na tej stronie meta.programmers.stackexchange.com/questions/2442/… Możesz po prostu ponownie sformułować pytanie. meta.programmers.stackexchange.com/questions/6967/…
Snoop
3
W ramach kontynuacji, czy dobrym pomysłem byłoby mieć flagę w żądaniu, aby (na przykład) aplikacja mobilna mogła zwrócić cały obiekt w przypadku WiFi, ale tylko identyfikator w przypadku korzystania z danych komórkowych? Czy istnieje nagłówek, którego należy użyć, aby uniknąć zanieczyszczenia JSON?
Andrew mówi Przywróć Monikę
@AndrewPiliser Ciekawy pomysł, chociaż osobiście uważam, że najlepiej wybrać jedno podejście i trzymać się go. Następnie, gdy Twoja aplikacja się rozwija lub staje się popularniejsza, zoptymalizuj ją
iswinky
@AndrewPiliser Twój pomysł jest bardzo podobny do UPDATE/INSERT ... RETURNINGwariantu Postgresql dla SQL. Jest to bardzo przydatne, zwłaszcza, że ​​utrzymuje przesyłanie nowych danych i żądanie zaktualizowanej wersji atomowej.
beldaz

Odpowiedzi:

31

Jak w większości rzeczy, to zależy. Kompromisem jest łatwość użycia w porównaniu z rozmiarem sieci. Klienci mogą zobaczyć bardzo dobrze utworzony zasób. Może zawierać pola wypełnione przez serwer, takie jak czas ostatniego utworzenia. Ponieważ wydaje się, że włączasz idzamiast używać hateoas, klienci prawdopodobnie będą chcieli zobaczyć identyfikator zasobu, który właśnie POSTedytowali.

Jeśli nie uwzględnisz utworzonego zasobu, nie twórz arbitralnej wiadomości. Pola 2xx i Lokalizacja są wystarczającymi informacjami dla klientów, aby mieć pewność, że ich żądanie zostało poprawnie obsłużone.

Eric Stein
źródło
+1 Cel nienawiści, polegający na tym, że klient nie pozwala na tworzenie identyfikatorów URI, można również osiągnąć, umożliwiając klientowi wypełnienie szablonów adresów URL podanych przez serwer określonymi identyfikatorami. Tak, klient „komponuje”, ale tylko w sposób „wypełnij puste pola”. Chociaż nie jest to HATEOAS, osiąga cel i sprawia, że ​​praca z obiektami, które mają (dużą) liczbę uri „akcji”, jest nieco mniej wrażliwa na pasmo, nie mówiąc już o umieszczeniu tych obiektów na (dużej) liście.
Marjan Venema
3
+1 do porady „nie twórz arbitralnej wiadomości”
HairOfTheDog
Czy „brak arbitralnego komunikatu” koncentruje się na komunikatach łańcuchowych lub jakiejkolwiek wartości zwracanej, która nie jest utworzonym zasobem? Skupiam się na przypadkach, w których zwracamy identyfikator utworzonego zasobu (ale nie samego zasobu) i zastanawiałem się, gdzie to pasuje.
Flater
12

Osobiście zawsze tylko wracam 200 OK.

Cytując twoje pytanie

Biorąc pod uwagę fakt, że użytkownik i tak wie, jaki obiekt próbuje utworzyć / zaktualizować.

Po co dodawać dodatkową przepustowość (za którą trzeba zapłacić), aby powiedzieć klientowi, co już wie?

Mawg
źródło
1
Tak właśnie myślałem, jeśli jest niepoprawny, możesz zwrócić wiadomości sprawdzające poprawność, ale jeśli jest poprawny i zostanie utworzony / zaktualizowany, sprawdź kod stanu HTTP i pokaż użytkownikowi komunikat np. „Brawo” na podstawie tego
isinkinky
3
Zobacz stackoverflow.com/a/827045/290182 odnośnie 200/ 204 No Contentaby uniknąć mylenia jQuery i tym podobnych.
beldaz
10

@ iswinky Zawsze odsyłam ładunek w przypadku zarówno POST, jak i PUT.

W przypadku POST możesz utworzyć encję z wewnętrznym identyfikatorem lub UUID. Dlatego sensowne jest odesłanie ładunku.

Podobnie w przypadku PUT możesz zignorować niektóre pola użytkownika (powiedzmy niezmienne wartości) lub w przypadku PATCH dane mogą zostać zmienione również przez innych użytkowników.

Odesłanie danych w takim stanie, w jakim były przechowywane, jest zawsze dobrym pomysłem i zdecydowanie nie szkodzi. Jeśli dzwoniący nie potrzebuje tych zwracanych danych, nie będzie ich przetwarzał, ale po prostu zużyje kod statusu. W przeciwnym razie mogą wykorzystać te dane jako coś do aktualizacji interfejsu użytkownika.

Tylko w przypadku USUWANIA nie odesłałbym ładunku i zrobiłbym albo 200 z treścią odpowiedzi, albo 204 bez treści odpowiedzi.

Edycja: Dzięki niektórym komentarzom z dołu redaguję swoją odpowiedź. Nadal jestem przy sposobie projektowania moich interfejsów API i wysyłania odpowiedzi, ale myślę, że sensowne jest zakwalifikowanie niektórych moich myśli projektowych.

a) Kiedy mówię „odeślij ładunek”, tak naprawdę miałem na myśli odesłać dane zasobu, a nie ten sam ładunek, który przyszedł w żądaniu. Np .: jeśli wyślesz ładunek tworzenia, mogę w backendie utworzyć inne podmioty, takie jak UUID i (być może) znaczniki czasu, a nawet niektóre połączenia (graficzne). Odesłałbym to wszystko w odpowiedzi (nie tylko ładunek żądania, jaki jest - co jest bezcelowe).

b) Nie wysyłałbym odpowiedzi w przypadku, gdy ładowność jest bardzo duża. Omówiłem to w komentarzach, ale chciałbym ostrzec, że dołożę wszelkich starań, aby zaprojektować moje interfejsy API lub zasoby tak, aby nie musiały mieć bardzo dużych obciążeń. Próbowałbym podzielić zasoby na mniejsze i łatwe do zarządzania jednostki, tak aby każdy zasób był zdefiniowany przez 15-20 atrybutów JSON i nie był większy.

W przypadku, gdy obiekt jest bardzo duży lub obiekt nadrzędny jest aktualizowany, wówczas odsyłam zagnieżdżone struktury jako hrefs.

Najważniejsze jest to, że zdecydowanie spróbuję odesłać dane, które mają sens, aby konsument / interfejs użytkownika natychmiast przetworzyły i wykonały akcję atomową interfejsu API zamiast iść i pobrać 2-5 dodatkowych API tylko w celu zaktualizowania interfejsu użytkownika po tworzenie / aktualizacja danych.

Interfejsy API między serwerami mogą o tym myśleć inaczej. Skupiam się na interfejsach API, które zwiększają komfort użytkowania.

ksprashu
źródło
Widzę wiele sytuacji, w których odesłanie całego ładunku jest złym pomysłem, gdy ładunek jest duży.
beldaz,
2
@beldaz całkowicie się zgadza. YMMV oparty na projekcie interfejsu API REST. Zasadniczo unikam bardzo dużych obiektów i dzielę je na szereg podrzędnych zasobów / PUT. Jeśli ładunek jest bardzo duży, istnieją lepsze sposoby, aby to zrobić, i tam chciałbyś zrobić HATEOAS (jak mówi Marjan powyżej), w którym zwracasz odwołanie do obiektu zamiast do samego obiektu.
ksprashu
@ksprashu: „Dlatego sensowne jest odesłanie ładunku” - uważam, że jest to zły pomysł, ponieważ w ten sposób zasób można uzyskać na wiele sposobów: poprzez GET, jako odpowiedź POST, jako odpowiedź PUT. Oznacza to, że klient otrzymuje 3 zasoby o potencjalnie innej strukturze. Jeśli tak, jakbyś zwrócił tylko URI (lokalizację), bez treści, jedynym sposobem na uzyskanie zasobu byłby GET. Dzięki temu klient zawsze uzyska spójne odpowiedzi.
mentallurg
@mentallurg Zdaję sobie sprawę, że mogłem nie sformułować tego prawa. Dzięki za zwrócenie na to uwagi. Zredagowałem swoją odpowiedź. Daj mi znać, jeśli ma to większy sens.
ksprashu
Tak długo, jak wdrażasz usługę do pracy w domu, tak naprawdę nie ma to znaczenia. Rób, jak chcesz. Oszczędzaj czas
mentallurg
9

Odwołując się do standardów RFC łącza , powinieneś zwrócić 201 (utworzony) status po pomyślnym zapisaniu zasobu żądania za pomocą Post. W większości aplikacji identyfikator zasobu jest generowany przez sam serwer, dlatego dobrą praktyką jest zwrócenie identyfikatora utworzonego zasobu. Zwrócenie całego obiektu stanowi obciążenie dla żądania Post. Idealną praktyką jest zwrócenie adresu URL nowo utworzonego zasobu.

Na przykład możesz odwołać się do poniższego przykładu, który zapisuje obiekt pracownika w bazie danych i zwraca adres URL nowo utworzonego obiektu zasobu w odpowiedzi.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Ten punkt końcowy odpoczynku zwróci odpowiedź jako:

Status 201 - UTWORZONY

Lokalizacja nagłówka → http: // localhost: 8080 / workers / 1

Shubham Bondarde
źródło
Ładnie i czysto - podążę za tym od teraz i dalej
Hassan Tareq
0

Chciałbym uzależnić ładunek w treści zwrotnej od parametru HTTP.

Najczęściej najlepiej jest zwracać konsumentowi API jakąś treść, aby uniknąć niepotrzebnych podróży w obie strony (jeden z powodów istnienia GraphQL).

W rzeczywistości, ponieważ nasze aplikacje stają się bardziej obciążone danymi i rozproszone, staram się przestrzegać następujących wskazówek:

Moja wytyczna :

Za każdym razem, gdy istnieje przypadek użycia, który wymaga GET natychmiast po POST lub PUT, jest to przypadek, w którym najlepiej byłoby po prostu zwrócić coś w odpowiedzi POST / PUT.

Jak to się robi i jaki typ treści zwraca z PUT lub POST, jest to specyficzne dla aplikacji. Byłoby interesujące, gdyby aplikacja mogła sparametryzować typ „treści” w treści odpowiedzi (czy chcemy tylko lokalizacji nowego obiektu, niektórych pól, całego obiektu itp.)

Aplikacja może zdefiniować zestaw parametrów, które może otrzymać test POST / PUT w celu kontrolowania rodzaju „treści” zwracanej w treści odpowiedzi. Lub może zakodować jakieś zapytanie GraphQL jako parametr (widzę, że jest to przydatne, ale także staje się koszmarem konserwacyjnym).

Tak czy inaczej, wydaje mi się, że:

  1. OK (i najprawdopodobniej pożądane) jest zwrócenie czegoś w treści odpowiedzi POST / PUT.
  2. Sposób ten jest specyficzny dla aplikacji i prawie niemożliwy do uogólnienia.
  3. Domyślnie nie chcesz zwracać dużego kontekstu (hałas uliczny, który pokonuje cały powód odejścia od POST, a następnie GET).

Więc 1) zrób to, ale 2) zachowaj prostotę.

Inną opcją, którą widziałem, są ludzie tworzący alternatywne punkty końcowe (powiedzmy, / klienci dla POST / PUT, którzy nic nie zwracają w treści i / customer_with_details dla POST / PUT dla / klientów, ale zwracają coś w treści odpowiedzi).

Unikałbym jednak takiego podejścia. Co dzieje się, gdy musisz zwrócić inny rodzaj treści? Jeden punkt końcowy na typ zawartości? Nie można skalować ani konserwować.

luis.espinal
źródło