Mało granularne statusy HTTP ustalone przez architekta

1

Proszę o wyjaśnienie, obecnie nasza aplikacja backendowa (duże korpo, niezdbędna w głównym procesie biznesowym firmy) komunikuje sie z frontem za pomoca restApi- standard. Przez architekta został narzucony styl logowania błędów, który mi się nie specjalnie podoba i nie jest tym co widziałem na kursach i poprzednich projektach.

Generalnie zwracane są tylko 2 typy błędów:

  1. 200 kiedy wszystko poszlo ok
  2. 404 - kiedy coś poszło nie tak (user nie mial dostepu do zasobow, nie udalo sie pobrac danych, zły parametr) rzucany jest zawsze ten sam wyjatek tzn. IllegalArgumentException i użytkownikowi w zależności od kontekstu wyświetlany jest odpowiedni komunikat na UI.

Dla szybkosci dostarczania nowych funkcjoanlosci to rozwiazanie ma sens, jednak- widzę kilka zagrożeń:

  1. Aplikacja w przysłości będzie trudniejsza do utrzymania
  2. Napisanie testy integracyjne będą mniej dokładne
  3. Rzucanie zawsze tego samego błędu powoduje bałagan w logach

Te 2 powyzsze argumenty nie przekonują architekta (aplikacja działą zgodnie z załozeniami), PO się cieszy, bo dostarczane są szybko funkcjonalności, a testerzy manualnie trochę nieogarniają.

Zastanawiam sie, czy to ja jestem w błędzie czy może mam racje?

3

Zacznijmy od tego że rzucanie IAE na nieprawidłowy input z zewnątrz to użycie wyjątku niezgodnie z przeznaczeniem. Ten wyjątek jest po to aby sygnalizować błędy programisty. Jeśli ten wyjątek poleci gdzieś w kodzie, to zawsze traktuję to jako bug. Do obsługi błędów z zewnątrz służą wyjątki checked.

Co do samego kodu błędu to faktycznie zdaje się że http oferuje tych kodów trochę więcej i fajnie by było ich używać, ale nie są one jakoś bardzo granularne. Pewnie większość błędów kończyłaby się HTTP 400 Bad Request.

2

Nie widzę zalet takiego podejścia w stosunku do bardziej granularnego określania co poszło nie tak. Jedyne co mi przychodzi do głowy, to jakieś względy bezpieczeństwa i brak chęci ujawniania szczegółów problemu. Nie bardzo widzę też oszczędność czasu i spodziewałbym się raczej wzrostu nakładu pracy po stronie frontu, bo tam programista też musi się domyślać, co się zrąbało.
No i ogólnie to architekt powinien uzasadnić dlaczego wybrał takie a nie inne rozwiązanie.

1

Nie ma specjalnego znaczenia jakie kody są zwracane.

Nie wiem czy dodanie więcej zwracanych statusów http by pomogło w czymkolwiek - to jest tylko zwracamy int. Równie dobrze mógłbyś zawsze zwracać 200, ale architekt pewnie dodał 404 żeby można było używać .catch() bez mapowania.

Moim zdaniem tak jak to opisałeś to jest git. Pamiętaj że HTTP w tym wypadku to jest tylko pośrednik między logiką servera i logiką klienta - są miejsca gdzie rest się nie nadaje, i moim zdaniem Twoja aplikacja to jedno z takich miejsc.

3

W jaki sposób zwracanie jednego kodu HTTP wymiernie wpływa na development?

Anyway, nie wiem skąd u wyżej wypowiadających się userów taka lekka ręka przy tym zwracaniu błedów.

Jest standard, są pewnego rodzaju konwencje i dobre praktyki to warto je stosować.

Już widywałem takie API gdzie ludziki sobie redefiniowały znaczenie tych kodów np. PaymentRequired oznaczał NotFound czy coś takiego i było to bardzo słabe.

Napisanie testy integracyjne będą mniej dokładne

W jaki sposób testy będą mniej dokładne w sytuacji gdy zwracasz tylko jeden kod dla wszystkich błędów? wtedy będziesz się kierował zawartością responsa.

3
masjav napisał(a):

404 - kiedy coś poszło nie tak (user nie mial dostepu do zasobow, nie udalo sie pobrac danych, zły parametr) rzucany jest zawsze ten sam wyjatek tzn. IllegalArgumentException i użytkownikowi w zależności od kontekstu wyświetlany jest odpowiedni komunikat na UI.

Jeden z najgorzej wybranych kodów. Co to znaczy 404, to wie blondynka w sekretariacie

Riddle napisał(a):

Nie ma specjalnego znaczenia jakie kody są zwracane.

Nie wiem czy dodanie więcej zwracanych statusów http by pomogło w czymkolwiek - to jest tylko zwracamy int. Równie dobrze mógłbyś zawsze zwracać 200

I jest taki sposób, a nawyzywa się GraphQL, zawsze zwraca 200
Tyle że to już nie jest REST

masjav napisał(a):

Zastanawiam sie, czy to ja jestem w błędzie czy może mam racje?

Masz rację.

Kolejny przykład - który będę kolekcjonował w głowie - patologicznego REST-a, , przypomniałem sobie zasłyszane, zgwałconego REST-a. Tym razem ten gwałt jest od innej strony (w inną dziurkę *) ), ale nim jest.

Kody dla REST są jasno określone, prosta wrzutka w google https://restfulapi.net/http-status-codes/
REST od samych fundamentów ma silnie polegać na cechach protokołu HTTP (i tu ogromnie się różni od innych na nośniku HTTP, np GraphQL)
Zwłaszcza grupa 4xx ma ciekawą logikę, choć 2xx i 3xx też ma to przemyślane. Piątki to piątki, coś się je...

Problemem REST jest to, że nie stoi za tym jakiś wyrazisty komitet standaryzacyjny, i każdy mały jaś robi po swojemu. Moze nie tylko jaś, ale janusz

EDIT: Pora na konkluzję: zmienić nie możesz, myśl o tym jako "zgwałcony REST" - nie pierwszy i nie ostatni - i tyle

*) w Toruniu to objaśnią lepiej

3

@Riddle: No jak podpinasz jakiś front, do zrobionego przez kogoś innego backendu, to jednak fajnie mieć szybką informację typu 404 zasób nie istnieje, 401 uwierzytelnij się itp. Zamiast 4xx coś zrąbałeś, domyśl się. I jakoś prościej to przeczytać, niż próbować zrozumieć czyjeś logi, na serwerze do którego nie masz dostępu.

1

Ale dlaczego domyślać się? Przecież wraz z kodem 4xx można wysłać body z jsonem opisującym błąd. I tam można nie tylko komunikat włożyć, ale też nazwę wyjątku, wewnętrzny kod błędu i masę innego kontekstu.

Dlatego tym bardziej uważam, że użycie IAE wewnątrz serwera do tego jest złe, bo IAE niesie za mało informacji (no i jest unchecked). Do tego trzeba zrobić sobie wyjątek domenowy, jakieś InvalidRequestException czy coś.

2

@Krolik:

A jaki problem jest zwrócić odpowiedni kod błędu wraz z body?

Są standardy i warto się ich trzymać, każdemu jest w ten sposób łatwiej.

Jakiś nawet teraz wymyślony z kosmosu argument mi przyszedł:

bo toole nie wiem... statystyki na jakimś load balancerze czy HTTP serwerze, może by chociaż ładniej wykresy generowały, a nie wszystko upchną pod jeden kod HTTP ;)

1

Taki problem, że kody HTTP są bardzo ubogie i nie obejmują wielu sytuacji.
Poza tym ja nie twierdzę, żeby zwracać odpowiednią informację w body ZAMIAST odpowiedniego kodu HTTP, tylko OPRÓCZ.

Inna rzecz, że ja w ogóle nie rozumiem po co cała ta rozkmina - przecież użycie odpowiednich kodów HTTP to nie jest jakieś rocket surgery wymagające miesiąca pracy seniora.

0

I to sprawia że lepiej jeszcze bardziej upchnąć wszystko pod np. BadRequest?

Generalnie jest jeszcze inna dyskusja w temacie HTTP codów.

Using HTTP status codes in your responses is a trap. It conflates the API transport with the actual semantics of the API. The goal of HTTP error responses is to say that something went wrong in the transport layer. The goal of API error responses is to say that something went wrong in your service. For example, your HTTP REST server may be perfectly fine, but your back end DB may be misbehaving. Having separate API level error responses, for example an explicit field called "error" in your JSON response, I consider to be best practice. Frequently your client needs to know the difference, for example to determine what kind of error to return to the user or what retry strategy to use.

In typical HTTP REST services this transport/API error split makes it really easy to create client code which only needs to check two conditions - if the HTTP response code is 200 or not, and if the error value is set or not. You also don't have to shoehorn your error handling into the very limited set of errors provided by the HTTP protocol.

The other real-world advantage of this is that when you outgrow HTTP as the transport protocol for performance reasons this makes porting the API really easy to, e.g. protobuf RPC, or even raw TCP. The error is already defined as part of the API and you don't need to rewrite all your client code to deal with mapping multiple HTTP response codes to your new transport. It's good future proofing I've seen pay off in a at least a couple of real-world cases.

Bottom line - your server should always return HTTP status 200 and a separate API error response.

coś w tym jest, aczkolwiek na ten moment nie jest to tak stosowane, i jak pisałem wyżej - aktualne podejścia ułatwia debug, analizy, etc.

1

jest więcej kodów błędu ale jak aplikacja juz  działa od dłuższego czasu to zmiana teraz może namieszać w zupełnie niepodziewanym miejscu
warto by dodać dodatkowe pole z kodem w nagłówku odpowiedzi, nie w body
Http 418 ;)

1

Co do braku odpowiednich statusów - Microsoft w ISSie używa subcodów np.

400.1 	Invalid Destination Header
400.2 	Invalid Depth Header
400.3 	Invalid If Header
400.4 	Invalid Overwrite Header
400.5 	Invalid Translate Header
400.6 	Invalid Request Body
400.7 	Invalid Content Length
400.8 	Invalid Timeout
400.9 	Invalid Lock Token
401.1 	Logon failed 	The logon attempt is unsuccessful probably because of a user name or a password that is invalid.
401.2 	Logon failed due to server configuration 	This HTTP status code indicates a problem in the authentication configuration settings on the server.
401.3 	Unauthorized due to ACL on resource 	This HTTP status code indicates a problem in the NTFS file system permissions. This problem may occur even if the permissions are correct for the file that you try to access. For example, this problem occurs if the IUSR account doesn't have access to the C:\Winnt\System32\Inetsrv directory.
401.4 	Authorization failed by filter 	An Internet Server Application Programming Interface (ISAPI) filter doesn't let the request be processed because of an authorization problem.
401.5 	Authorization failed by ISAPI/CGI application 	An ISAPI application or a Common Gateway Interface (CGI) application doesn't let the request be processed because of an authorization problem.
401.501 	Access Denied: Too many requests from the same client IP; Dynamic IP Restriction Concurrent request rate limit reached. 	
401.502 	Forbidden: Too many requests from the same client IP; Dynamic IP Restriction Maximum request rate limit reached. 	
401.503 	Access Denied: the IP address is included in the Deny list of IP Restriction 	
401.504 	Access Denied: the host name is included in the Deny list of IP Restriction 	
2

@Królik: Owszem, sam kod http niesie w sobie mało informacji i można dodać coś ekstra. Ale to jeszcze nie powód, żeby tę ilość informacji dodatkowo zmniejszać wprowadzając dodatkowo zamieszanie. Dla mnie otrzymanie 404 z serwera oznacza konkretnie, że pod podaną w żądaniu ścieżką nie występuje zasób/usługa z której chcę skorzystać. W opisanym wyżej przypadku nie wiem nawet, że to jest błąd po stronie klienta, bo łapanie wszystkiego na górze i zamienianie w 404, równie dobrze może wystąpić w przypadku kiedy padnie połączenie backendu z bazą danych.

1

@masjav: Używasz protokołu HTTP do komunikacji klienta z serverem. Architekt użył bardzo prostego sposobu komunikacji (200 oraz 404), a Ty pytasz czy powinniście na to narzucić bardziej skomplikowany i wymagający constraint (w tym przypadku REST), co sprowadza się po prostu do dodania: więcej status code'ów do obsłużenia i więcej nazw metod do obsłużenia.

Widzę, że jesteś osobą która nie potrafi rozróżnić REST od HTTP i dlatego Ci się wydaje że Twój architekt nie ma racji.

Zdradzę Ci bardzo niepopularny fakt: możesz użyć http jako zwykłego protokołu - bez rest, bez graphql i bez soap - możesz to zrobić :>

5

@Riddle

Zdradzę Ci bardzo niepopularny fakt: możesz użyć http jako zwykłego protokołu - bez rest, bez graphql i bez soap - możesz to zrobić :>

i jakie widzisz zyski z stosowania dwóch status codów do wszystkiego zamiast wielu?

że łatwiej obsłużyć na froncie? i tyle?

2
WeiXiao napisał(a):

@Krolik:

A jaki problem jest zwrócić odpowiedni kod błędu wraz z body?

Są standardy i warto się ich trzymać, każdemu jest w ten sposób łatwiej.

Jakiś nawet teraz wymyślony z kosmosu argument mi przyszedł:

bo toole nie wiem... statystyki na jakimś load balancerze czy HTTP serwerze, może by chociaż ładniej wykresy generowały, a nie wszystko upchną pod jeden kod HTTP ;)

@Krolik: +1 @piotrpo +!

Jestem zdziwiony, że zwracanie znaczącego kodu (na tyle, na ile kod może nieśc informację, oczywiście rozumiem inne wypowiedzi) miałoby być jakoś szczególnie uzasadniane. To są oczywiste oczywistości.
Choćby na debugerze ustawienie warunkowego breakpointu na gotowym integerze v/s grzebanie w dżejsonach gdzie niby jest ta informacja

Oczywiste oczywistości

WeiXiao napisał(a):

Generalnie jest jeszcze inna dyskusja w temacie HTTP codów.

The other real-world advantage of this is that when you outgrow HTTP as the transport protocol for performance reasons this makes porting the API really easy to, e.g. protobuf RPC, or even raw TCP. The error is already defined as part of the API and you don't need to rewrite all your client code to deal with ...

Binarne protokoły / czy generowane z IDL protokoły / protokoły niezależne od HTTP to ogromna i ciekawa dziedzina.
Widzenie wad Cobry (nie używałem) czy SOAP/WSDL (używałem, mam dobre wrażenia) spowodowało, przy odbiciu wahadła w drugą stronę, wylanie dziecka z kąpielą. Wpadliśmy w programowanie na stringach, i wszyscy klaszczą brawo.

Ciekawe, kiedy ktoś ze znaczących influencerów IT powie, że król REST jest nagi. Skądinąd, bajka ma cotygodniowe meet-upy w zespołach, polecam.
Na razie powiedzieć to głośno, to skazanie na banicję.

Aha, jeszcze jedno. Widzę (spodziewałem sie tego) bardzo duże poparcie bylejakiego REST-a (naruszjącego standardy, fakt, zbyt słabo spisane)
Widze w tym coś w rodzaju branżowej (przynajmniej) nieszczerości. Jesteśmy w deklaracjach tacy w pysku mocni pro-standardowi i w ogóle, jak to standardy są wartością najwyższą samą w sobie, a w realnym życiu mamy je w d/

Miejwny format w jajach. Wymyślasz własny protokołól, i super że wymyślasz, neiktóre sie przyjmą. Ale miej tyle odwagi, aby nazwać go po swojemu, a nie "podłączać się" to największą grupę.

1

Spróbujmy zebrać wady i zalety

Plusy używania jednego kodu do errorów:

  • Łatwiej pisać obsługę na froncie - jedna, spójna dla wszystkiego.

Plusy używania wielu kodów do errorów:

  • Toole lepiej rozumieją te kody niż dane z body, więc wykresiki ładniej ci się powinny rysować, a przynajmniej nie będziesz musiał tego przetwarzać
  • Łatwiej przeanalizować logi, bo nie będziesz musiał ich przetworzyć aby pogrupować/grepować po body po jakimś wewnętrznym kodzie błędu. Jeżeli w ogóle logujesz body i masz dostęp :)
  • Nie trzeba analizować zawartości aby mieć wskazówkę na to co poszło nie tak - NotFound, Auth, PaymentRequired, Unavailable For Legal Reasons etc.

Aczkolwiek nie uważam że OP ma racje pisząc:

Aplikacja w przysłości będzie trudniejsza do utrzymania
Napisanie testy integracyjne będą mniej dokładne

Wręcz aplikacje może być łatwiejsza do utrzymania, bo obsługa błędów będzie uproszczona.

Co do zasady żadne z podejść nie jest "prawidłowe", a bardziej jedno jest bardziej standardowe niż drugie, ale oba mają jakiś sens.

2
WeiXiao napisał(a):

Spróbujmy zebrać wady i zalety

Plusy używania jednego kodu do errorów:

  • Łatwiej pisać obsługę na froncie - jedna, spójna dla wszystkiego.

Użycie na froncie status >= 400 zamiast status == 404 nie wydaje się przesadnym rozbuchaniem logiki.

Natomiast problem jest w innym miejscu. Owszem architekt odpowiada za wyznaczenie standardu, ale również za wyjaśnienie dlaczego akurat taką decyzje podjął.

Tutaj mamy:

  • decyzję ważną, bo dotyczy kontraktu pomiędzy komponentami systemu, więc będzie też trudna do zmiany w przyszłości.
  • łamiącą jakieś tam standardy, więc przydałoby się mieć jakiś dobry powód i cel, żeby od tych standardów odstąpić
  • programistę, który nie wie dlaczego ta decyzja została podjęta, więc z jednej strony się wkurza, z drugiej nie jest w stanie uzyskać potencjalnych korzyści wynikających z tego uproszczenia.
0

Zakładam, że te reguły nie są wyryte w kamieniu i gdybys pokazał zasadny argument, dałoby się to zaimplementować. W jakiej konkretnie sytuacji obecne zasady okazały się niewystarczające?

99% przypadków to naprawdę jest 200/404 ;D

0

W idealnym świecie REST API powinno być 'self-explanatory' więc powinno korzystać się z urozmaiceń jakie nam oferuje, czyli pownno być ładnie zorganizowane i opisane.
Jeżeli jesteś full-stackiem to wiesz o co chodzi ale jeśli różne osoby są odpowiedzialne za backend/frontend to brak takich rzeczy jak:

  • odpowiednie statusy HTTP, brak headerów, brak kodów np. błędów (machine-readable), brak wiadomości błędów (human-readable) to strzelanie sobie w stopę ale wiadomo, że outscoring rządzi się swoimi prawami => mniej rzeczy do ogarnięcia = szybciej dowieziony projekt
1
Zachu13x napisał(a):

W idealnym świecie REST API powinno być 'self-explanatory' więc powinno korzystać się z urozmaiceń jakie nam oferuje, czyli pownno być ładnie zorganizowane i opisane.
Jeżeli jesteś full-stackiem to wiesz o co chodzi ale jeśli różne osoby są odpowiedzialne za backend/frontend to brak takich rzeczy jak:

  • odpowiednie statusy HTTP, brak headerów, brak kodów np. błędów (machine-readable), brak wiadomości błędów (human-readable) to strzelanie sobie w stopę ale wiadomo, że outscoring rządzi się swoimi prawami => mniej rzeczy do ogarnięcia = szybciej dowieziony projekt

Odpowiednie zarządzanie, utrzymanie i testowanie tych headerów code'ów i całego REST to również jest dużo pracy, wysiłku, potencjalnych błędów, czasu i kasy. W niektórych przypadkach gra nie jest warta świeczki.

Jeśli wystawiasz API dla "nieznajomego" (jak np serwis online wystawia API dla publicznych klientów), to REST API może się opłacić: twórcy wkładają np dodatkowe 20h godzin pracy żeby usprawnić API kodami/statusami/pathami, po to żeby klienci mogli oszczędzić 10 minut na czytaniu dokumentacji, i to może się opłacać. Bo Twórca API poświęci raz te 20h, a powiedzmy tysiąc klientów z tego skorzysta.

Ale kiedy tworzymy aplikację webową, i z naszego backendu korzysta tylko jeden klient - mianowicie nasz frontend; to nie opłaca się tworzyć takiego granularnego (czyt. skomplikowanego API).

0

no i z takiego podejścia się bierze refakturing i spaghetti, można pisać dobrze od początku ale dla swoich to się nie opłaca

1
Miang napisał(a):

no i z takiego podejścia się bierze refakturing i spaghetti, można pisać dobrze od początku ale dla swoich to się nie opłaca

Brak REST to dla Ciebie spahgetti? No to gratuluję.

Chodzi o to że w pewnych wąskich projektach: jak np backend i front jednej aplikacji webowej, pewne operacje i procesy nie mają sensu. Dodanie statusów, nazw metod i pathów w Rest, owszem dodaje pewne informacje, ale jest to ogromny koszt, koszt który może ale nie musi się opłacić, i w większości przypadków się nie opłaca. Poza tym, jeśli to API HTTP jest proste, to wprowadzenie do niego REST'a wręcz może je skomplikować, i wtedy to API będzie gorsze.

0

ja akurat postulowałam info o rodzaju błędu w headerze i to jak najbardziej ma sens

0

@Riddle: czym dla Ciebie jest Rest i nie Rest? W przykładzie którym opisałem brakuje tylko obsługi błędów? Czy to znaczny ze jest to zwykle API nierestowe? Jeśli coś mylę to proszę mnie popraw;)

0
masjav napisał(a):

W przykładzie którym opisałem brakuje tylko obsługi błędów?

Statusy HTTP w REST to nie są błędy - to że myślisz o nich jak o errorach to kolejny sygnał że jednak tego nie rozumiesz do końca.

REST to jest traktowanie informacji jak zasoby.

masjav napisał(a):

Czy to znaczny ze jest to zwykle API nierestowe?

No tak, API po HTTP.

masjav napisał(a):

@Riddle: czym dla Ciebie jest Rest i nie Rest?

Aktualnie aplikacje webowe mogą się komunikować z backendem albo po HTTP albo po WebSocket - aplikacja OP komunikuje się po HTTP. REST to natomiast to jest zbiór ograniczeń i wytycznych które nakładamy na HTTP, tak żeby ścieżki HTTP zachowywały się jak zasoby - czyli jak pliki, dodawanie użytkowników i eventów powinno wyglądać jak tworzenie pliku user i event.

Dodanie tych ograniczeń i wytycznych zajmuje czas i wysiłek. W pewnych wypadkach takie ograniczenia i wytyczne pomagają, w innych nie.

W mojej ocenie, w przypadku programu przedstawione OP, to by tylko zaszkodziło.

1

Statusy http FTW, ale one nie wystarczą do czegoś wiecej niz trywialne api. Polecam zapoznac sie (i zacząć stosować w nowych api) istniejący, dobrze opisany standard Problem Details for HTTP APIs, Springboot 3.0 wspiera to natywnie, Zalando zrobilo bibliotekę i starter do 2.X, do Quarkusa tez jest rozszerzenie.

2

Szczerze to spotkałem się z takim podejściem, że jeśli zwracasz zbyt dokładne statusy, to da się wywnioskować pewne dane, czy błędne hasło, login, lub jakieś inne informacje.

I żeby to ukryć się stosuje jedne uniwersalne, wtedy nie da się wywnioskować jaki jest błąd, trochę zaleta, bo zewnętrzny obserwator nie ma pojęcia o niczym, a trochę minus, bo wewnętrzy programista ma problem zrozumieć co się stało lub ma trochę utrudnioną pracę.

Jako perfekcjonista pewnie bym pierwsze wybrał, ale jako hacker pewnie drugie, szczerze sam nie wiem, pewnie jakiś kompromis powinien być, lepiej raczej pierwszy sposób dobrze dokumentować błędy, ale też pewne krytyczne i mogące komuś bez uprawnień w czymś pomóc powinny być ukryte, czyli tak pół na pół.

2

dziwne, ze nikt nie wspomnial, ale...
po pierwsze rest ma 3 poziomy dojrzalosci
po drugie jak mamy odpowiedni kod nie musimy dodatkowo pobierać body (oszczednosc)

1 użytkowników online, w tym zalogowanych: 0, gości: 1