Buduję interfejs API, w którym użytkownik może poprosić serwer o wykonanie wielu akcji w jednym żądaniu HTTP. Wynik jest zwracany jako tablica JSON, z jednym wpisem na akcję.
Każda z tych akcji może zakończyć się niepowodzeniem lub odnieść sukces niezależnie od siebie. Na przykład pierwsza akcja może się powieść, dane wejściowe do drugiej akcji mogą być źle sformatowane i nie mogą się sprawdzić, a trzecia akcja może spowodować nieoczekiwany błąd.
Gdyby było jedno żądanie na akcję, zwróciłbym odpowiednio kody stanu 200, 422 i 500. Ale teraz, gdy jest tylko jedno żądanie, jaki kod stanu powinienem zwrócić?
Niektóre opcje:
- Zawsze zwracaj 200 i podaj bardziej szczegółowe informacje w ciele.
- Może postępujesz zgodnie z powyższą zasadą tylko wtedy, gdy w żądaniu jest więcej niż jedna akcja?
- Może zwróci 200, jeśli wszystkie żądania zakończą się powodzeniem, w przeciwnym razie 500 (lub inny kod)?
- Wystarczy użyć jednego żądania na akcję i zaakceptować dodatkowe koszty ogólne.
- Coś zupełnie innego?
Odpowiedzi:
Krótka, bezpośrednia odpowiedź
Ponieważ żądanie mówi o wykonaniu listy zadań (zadania są zasobem, o którym tu mówimy), to jeśli grupa zadań została przeniesiona do wykonania (to znaczy niezależnie od wyniku wykonania), byłoby rozsądne będzie status odpowiedzi
200 OK
. W przeciwnym razie, jeśli wystąpi problem, który uniemożliwiłby wykonanie grupy zadań, taki jak niepowodzenie sprawdzania poprawności obiektów zadania lub niektóre wymagane usługi nie będą dostępne, na przykład stan odpowiedzi powinien oznaczać ten błąd. W przeszłości, gdy rozpoczyna się wykonywanie zadań, ponieważ zadania do wykonania są wymienione w treści żądania, oczekiwałbym, że wyniki wykonania zostaną wyświetlone w treści odpowiedzi.Długa, filozoficzna odpowiedź
Ten dylemat występuje, ponieważ odwracasz się od tego, do czego został zaprojektowany HTTP. Nie współdziałasz z nim w celu zarządzania zasobami, a raczej używasz go jako narzędzia do zdalnego wywoływania metod (co nie jest bardzo dziwne, ale działa słabo bez wcześniej założonego schematu).
Biorąc powyższe pod uwagę, i bez odwagi, aby zamienić tę odpowiedź w długo opiniotwórczy przewodnik, poniżej przedstawiono schemat URI zgodny z podejściem do zarządzania zasobami:
/tasks
GET
wyświetla wszystkie zadania, paginowanePOST
dodaje jedno zadanie/tasks/task/[id]
GET
odpowiada obiektem stanu pojedynczego zadaniaDELETE
anuluje / usuwa zadanie/tasks/groups
GET
wyświetla wszystkie grupy zadań, paginowanePOST
dodaje grupę zadań/tasks/groups/group/[id]
GET
odpowiada stanem grupy zadańDELETE
anuluje / usuwa grupę zadańTa struktura mówi o zasobach, a nie o tym, co z nimi zrobić. To, co dzieje się z zasobami, dotyczy innej usługi.
Inną ważną kwestią do zrobienia jest to, że wskazane jest, aby nie blokować zbyt długo w module obsługi żądań HTTP. Interfejs HTTP, podobnie jak interfejs użytkownika, powinien być responsywny - w skali czasowej wolniejszej o kilka rzędów wielkości (ponieważ ta warstwa dotyczy IO).
Przejście w kierunku zaprojektowania interfejsu HTTP, który ściśle zarządza zasobami, jest prawdopodobnie tak trudne, jak oderwanie pracy od wątku interfejsu użytkownika po kliknięciu przycisku. Wymaga to, aby serwer HTTP komunikował się z innymi usługami w celu wykonania zadań zamiast wykonywania ich w module obsługi żądań. To nie jest płytkie wdrożenie, to zmiana kierunku.
Kilka przykładów zastosowania takiego schematu URI
Wykonywanie pojedynczego zadania i śledzenie postępu:
POST /tasks
z zadaniem do wykonaniaGET /tasks/task/[id]
do momentu, gdy obiekt odpowiedzi będziecompleted
miał wartość dodatnią, jednocześnie pokazując aktualny status / postępWykonanie pojedynczego zadania i oczekiwanie na jego zakończenie:
POST /tasks
z zadaniem do wykonaniaGET /tasks/task/[id]?awaitCompletion=true
dopókicompleted
ma wartość dodatnią (prawdopodobnie ma limit czasu, dlatego należy go zapętlić)Wykonywanie grupy zadań i śledzenie postępu:
POST /tasks/groups
z grupą zadań do wykonaniaGET /tasks/groups/group/[groupId]
do momentu, gdycompleted
właściwość obiektu odpowiedzi będzie miała wartość, pokazującą status pojedynczego zadania (na przykład 3 zadania ukończone z 5)Żądanie wykonania grupy zadań i oczekiwanie na jej zakończenie:
POST /tasks/groups
z grupą zadań do wykonaniaGET /tasks/groups/group/[groupId]?awaitCompletion=true
dopóki nie odpowie wynikiem oznaczającym zakończenie (prawdopodobnie ma limit czasu, dlatego należy go zapętlić)źródło
Głosowałbym za podzieleniem tych zadań na osobne wnioski. Jeśli jednak problemem jest zbyt wiele podróży w obie strony, natknąłem się na kod odpowiedzi HTTP 207 - Multi-Status
Skopiuj / wklej z tego linku:
źródło
207
wydaje się być tym, czego chce OP, ale naprawdę chcę podkreślić, że to prawdopodobnie zły pomysł, aby zastosować takie podejście z wieloma żądaniami w jednym. Jeśli chodzi o wydajność, powinieneś zająć się architekturą dla systemów skalowalnych poziomo w stylu chmurowym (co jest czymś, w czym systemy HTTP są świetne)Chociaż opcja wielu statusów jest opcją, zwróciłbym 200 (Wszystko jest dobrze), jeśli wszystkie żądania się powiodły, aw przeciwnym razie błąd (500, a może 207).
Standardowy przypadek powinien zwykle wynosić 200 - wszystko działa. Klienci powinni tylko to sprawdzić. I tylko jeśli wystąpił przypadek błędu, możesz zwrócić 500 (lub 207). Myślę, że 207 jest prawidłowym wyborem w przypadku co najmniej jednego błędu, ale jeśli widzisz cały pakiet jako jedną transakcję, możesz również wysłać 500. - Klient będzie chciał zinterpretować komunikat o błędzie w jakikolwiek sposób.
Dlaczego nie zawsze wysłać 207? - Ponieważ standardowe skrzynki powinny być łatwe i standardowe. Chociaż wyjątkowe przypadki mogą być wyjątkowe. Klient powinien czytać treść odpowiedzi i podejmować dalsze złożone decyzje, jeśli uzasadnia to wyjątkowa sytuacja.
źródło
Jedną z opcji byłoby zawsze zwrócenie kodu stanu 200, a następnie zwrócenie określonych błędów w treści dokumentu JSON. Tak właśnie zaprojektowano niektóre interfejsy API (zawsze zwracają kod stanu 200 i wysyłają błąd w treści). Aby uzyskać więcej informacji na temat różnych podejść, zobacz http://archive.oreilly.com/pub/post/restful_error_handling.html
źródło
200
aby wskazać, że wszystko jest w porządku, żądanie zostało odebrane i było prawidłowe , a następnie użyć JSON, aby podać szczegóły na temat tego, co wydarzyło się w tym żądaniu (tj. Wyniku transakcji).Myślę, że neilsimp1 jest poprawny, ale zaleciłbym przeprojektowanie przesyłanych danych w taki sposób, aby można było wysłać
206 - Accepted
i przetworzyć dane później. Być może z oddzwanianiem.Problem z próbą wysłania wielu akcji w jednym żądaniu polega na tym, że każda akcja powinna mieć swój własny „status”
Patrząc na import CSV (nie wiem tak naprawdę o czym jest OP, ale jest to prosta wersja). POST CSV i odzyskaj 206. Następnie później CSV można zaimportować, a status importu można uzyskać za pomocą GET (200) w odniesieniu do adresu URL, który pokazuje błędy według wierszy.
Ten sam wzór można zastosować do wielu operacji wsadowych
Kod obsługujący test POST musi jedynie zweryfikować, czy format danych operacji jest prawidłowy. W późniejszym czasie operacje mogą zostać wykonane. Na przykład w tle pracownika, dzięki czemu można łatwiej skalować. Następnie możesz sprawdzić status operacji, kiedy tylko chcesz. Możesz użyć odpytywania lub oddzwaniania, strumieni lub czegokolwiek, aby zaspokoić potrzebę wiedzieć, kiedy zestaw operacji zostanie zakończony.
źródło
Jest tu już wiele dobrych odpowiedzi, ale brakuje jednego aspektu:
Jakiej umowy oczekują Twoi klienci?
Kody zwrotne HTTP powinny wskazywać przynajmniej rozróżnienie sukces / porażka, a zatem odgrywać rolę „wyjątków biedaka”. Wtedy 200 oznacza „umowa całkowicie spełniona”, a 4xx lub 5xx wskazują na niedotrzymanie umowy.
Naiwnie oczekiwałbym, że kontrakt z twoją prośbą o wiele działań będzie „wykonywał wszystkie moje zadania”, a jeśli jedno z nich się nie powiedzie, wtedy prośba nie była (całkowicie) pomyślna. Zazwyczaj jako klient rozumiem 200 jako „wszystko w porządku”, a kody z rodziny 400 i 500 zmuszają mnie do myślenia o konsekwencjach (częściowej) awarii. Tak więc użyj 200 dla „wszystkich wykonanych zadań” i 500 plus opisowa odpowiedź w przypadku częściowej awarii.
Inną hipotetyczną umową może być „spróbuj wykonać wszystkie czynności”. To jest całkowicie zgodne z umową, jeśli (niektóre) działania zawiodą. Zawsze zwracasz 200 plus dokument wyników, w którym znajdziesz informacje o sukcesie / porażce dla poszczególnych zadań.
Więc jaki kontrakt chcesz przestrzegać? Oba są ważne, ale pierwszy (200 tylko w przypadku, gdy wszystko zostało zrobione) jest dla mnie bardziej intuicyjny i lepiej zgodny z typowymi wzorcami oprogramowania. A w (miejmy nadzieję) większości przypadków, w których usługa wykonała wszystkie zadania, klient może to łatwo wykryć.
Ostatni ważny aspekt: w jaki sposób przekazujesz klientom decyzję dotyczącą umowy? Np. W Javie użyłbym nazw metod takich jak „doAll ()” lub „tryToDoAll ()”. W HTTP można odpowiednio nazwać adresy URL punktów końcowych, mając nadzieję, że programiści klienta zobaczą, przeczytają i zrozumieją nazewnictwo (nie postawiłbym na to). Jeszcze jeden powód, aby wybrać umowę najmniej zaskoczenia.
źródło
Odpowiedź:
Kod stanu opisuje status jednej operacji. Dlatego sensowne jest posiadanie jednej operacji na żądanie.
Wiele niezależnych operacji łamie zasadę, na której oparty jest model żądanie-odpowiedź i kody statusu. Walczysz z naturą.
HTTP / 1.1 i HTTP / 2 znacznie zmniejszyły obciążenie żądaniami HTTP. Szacuję, że jest bardzo niewiele sytuacji, w których zalecane jest grupowanie niezależnych żądań.
To mówi,
(1) Możesz dokonać wielu modyfikacji za pomocą żądania PATCH ( RFC 5789 ). Wymaga to jednak, aby zmiany nie były niezależne; są stosowane atomowo (wszystko lub nic).
(2) Inni zwrócili uwagę na kod Multi-Status 207. Jest to jednak zdefiniowane tylko dla WebDAV ( RFC 4918 ), rozszerzenia HTTP.
Odpowiedź XML WebDAV 207 byłaby tak dziwna jak kaczka w interfejsie API innym niż WebDAV. Nie rób tego
źródło
Jeśli naprawdę potrzebujesz wielu akcji w jednym żądaniu, dlaczego nie zawinąć wszystkich akcji w transakcji w backend? W ten sposób albo wszyscy odnoszą sukces, albo wszyscy zawodzą.
Jako klient korzystający z interfejsu API mogę sobie poradzić z całkowitym powodzeniem lub niepowodzeniem wywołania interfejsu API. Trudno poradzić sobie z częściowym sukcesem, ponieważ musiałbym poradzić sobie ze wszystkimi możliwymi stanami wynikowymi.
źródło