Jak zwracać 404; czy stosować wyjątki jak zasobu o danym id nie ma?

0

Siema, z tej strony Cyckoben
I PYTANIE
Jak chcę zwrócić 404 Not Found to dodawać do tego jakieś body?

Z jednej strony niby co tam miałoby byc sensownego poza powtorzeniem statusu, kodu i datą. Z drugiej strony na stackoveflow zdania są podzielone.

return ResponseEntity.notFound().build();

Dodatkowo, taki builder nie pozwala dodać body, czyli jak rozumiem twórcy już nam uważają, że nie warto dodawać i mam się dostosować?

Czy może nie stosowac i obejśc to tak:

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(createErrorBody(HttpStatus.NOT_FOUND, exc, request));

II PYTANIE
Gdy np. user chce pobrać z api obiekt o id = 7, a takiego nie ma, to jak obslużyć to w kodzie? W każdym prawilnym tutorialu jest rzucanie wyjątku IllegalArgumentException
Trochę lipą to zalatuje, bo chyba wyjątki to służą czemuś innemu.

Dla przykładu aniserowicz w swoim kursie

Car car = repository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Car with this id wasn't found"));
3

Ad. 1. strzelam że ten builder jest ze springa, ale nie samym springiem człowiek żyje. Np Akka (którą też można używać w Javie) daje dowolność. W projektach gdzie pracowałem zwykle zwraca się jakiegoś UUIDa requestu żeby można było znaleźć to łatwo w logach

Ad. 2. Wolę używanie Option/Optional/Maybe (które sa stworzone do reprozentowania zasobu który może jest, a może nie jest), albo Either jak chce też mieć zapisany powód czemu nie ma zasobu

Dla przykładu aniserowicz w swoim kursie

aniserowicz chyba już nie programuje :P jak to że nie ma samochodu to koniec świata i nic z tym już nie zrobisz to można w ostateczności ten exception rzucić, ale jak masz np kilka możliwych źródeł skąd może pochodzić samochód to lepszy jest Optional.

W Javie to chyba było jakoś tak:

var maybeCar = repository.findById(id).or(repository2.findById(id)).or(repository3.findById(id))
4
  1. To juz twoja sprawa czy chcesz tam przekazać jakieś informacje czy nie
  2. Słuchaj się Aniserowicza który od 20 lat nie napisał linijki kodu xD Normalnie zwracasz jakis Optional<T> jak nie obchodzi cię co poszło źle, albo Either<SomeError,T> jak cię obchodzi. W pierwszym przypadku masz nawet springowe ResponseEntity.of(optionalResponse) które samo sobie ogarnie żeby zrobić 404 albo 200 z odpowiedzią
5

ad 1. to trochę zależy, co jest celem. Generalnie w czasach wszechobecnego RESTa 404 bez body jest OK, bo to po stronie klienta leży zadanie prezentacji faktu, że danego zasobu brak. Jednocześnie jeżeli klientem jest przeglądarka, to 404 z body pozwala na wyrenderowanie jakiejś sensownej strony, która np. będzie zawierać odnośniki powalające na powrót. Tak, wiem, można to ogarnąć domyślnymi error pages, ale nie zawsze tak się da.

ad 2. Sterowanie przepływem za pomocą wyjątków. Bardzo dobre rozwiązanie… jak chcesz pisać kiepski kod. Aniserowicz od dawna nic nie pisze, a w dodatku Java nie jest jego „macierzystą” technologią, więc nie brałbym tego za poważnie. Zwróć Optional, narzędzia sobie z nim poradzą.

0

Nie neguje podejścia chłopaków powyżej. Zauważyłem, że w większości projektów jest takie podejście jak napisał OP tzn. .orElseThrow do tego łapane w advice handlerze i zwrotka automatyczna 404 do klienta. Wychodzi na to, że 90% projektów robi źle ;) Subiektywnie, przyczynaą jest to, że jest to wygodne do większości crudów. Generalnie zgadzam się, ale nie zawsze łatwo/lub jest brzydko jest coś zrobić w fluent API za pomocą .map x5 i zwrócić Optional.
Przykładowo jeżeli mamy jakiś agregat i używamy tego mniej więcej w ten sposób:

Car car = repository.findById(command.getId())
                .orElseThrow(() -> new CarNotFoundException("Car with this id wasn't found")); //jak empty to handler łapie i 404
car.handle(command);
SomeEvent event = otherSyncServiceClient.call(command.getId(), car.getStatus());
car.handle(event);
repository.update(car);
return RequestResult.of(event);
3

Wychodzi na to, że 90% projektów robi źle

Nic w tym dziwnego. Piszą je często ludzie robiący kopiuj-wklei z hinduskiego tutoriala pisanego w czasach Javy 1.6 ;)

Nie wiem za bardzo co pokazuje ten twój przykład, bo równie dobrze można było napisać: return repository.findById(command.getId()).map(this::someMethod) gdzie someMethod zawiera

car.handle(command);
SomeEvent event = otherSyncServiceClient.call(command.getId(), car.getStatus());
car.handle(event);
repository.update(car);
return RequestResult.of(event);

i wychodzi w zasadzie na to samo.

Jedyna sytuacja kiedy realnie wygląda to brzydko, to kiedy masz np. do zebrania kilka wyników z których każdy to jakis Either, bo nagle masz cudo w stylu:

someMethod1()
    .flatMap(res1 -> someMethod2()
        .flatMap(res2 -> someMethod3()
            .flatMap(res3 -> someMethod4(res1,res2,res3)));
2

Jak dla mnie są trzy powody dla których nie chcesz w podanym przypadku rzucać wyjątku:

  1. Wyjątki powinny służyć dla wyjątkowych sytuacji - nie znam kontekstu podanego przykładu, ale zakładam, że szukamy samochodu po ID podanym przez użytkownika. A to oznacza, że zupełnie oczekiwaną sytuacją jest, że poda on id nieistniejącego samochodu i jest to prawidłowa ścieżka, a nie wyjątkowa. Wyjątek byłby ok jeśli już wcześniej w kodzie w jakiś sposób sprawdziłeś, że samochód istnieje.
  2. Wyjątki to nowe "go to" - bardzo cieżko połapać się w kodzie w którym zachowania są sterowane wyjątkami. Coś gdzieś leci i w innym miejscu (jeszcze najlepiej przez jakiś Bean springowy) jest łapane. Osoba czytająca kod nie dowie się jak on działa przez przejście metod od kontrolera w dół. Musi wiedzieć jeszcze o jakiś Controller Advice itd.
  3. Każdy taki wyjątek produkuje stacktrace, który jest po prostu słaby jeśli chodzi o performance. Wyobraź sobie, że to takiego API zaczyna uderzać ktoś z błędnym ID 1000 razy na sekundę - obsługa każdego takiego zapytania będzie generowała niepotrzebny stacktrace tylko po to, żeby zaraz wyjątek przechwycić w komponencie. Widziałem kiedyś porównanie wersji if(x == null) vs catch(NullPointerException) i pierwsza wersja miała czasy setki razy mniejsze.

Według mnie - jeśli zdajesz sobie sprawę z decyzji i potrafisz ją uargumentować to może to jest najprostsze podejście i trzeba z tym jechać. Np. w przypadku prostego CRUDa może się sprawdzić. Trzeba pamietać, że ostatecznie narzędzia są dla nas, a my powinniśmy dostarczać rozwiązania :)

0

Dobrze, ze sie przedstawiles. Zasada jest prosta zwracasz to co ci kaze klient, a jesli nie to patrz co napisalem wczesniej.

2

@victordeleco2:

Nie neguje podejścia chłopaków powyżej. Zauważyłem, że w większości projektów jest takie podejście jak napisał OP tzn. .orElseThrow do tego łapane w advice handlerze i zwrotka automatyczna 404 do klienta. Wychodzi na to, że 90% projektów robi źle

No akurat to, że programowanie na wyjątkach jest antywzorcem to było wiadomo od dawna - chyba w Clean Code sprzed kilkunastu lat o tym czytałem. Advice handler powinien służyć do łapania 500 i wyżej (korciło mnie, żeby napisać 500+ :D). Sytuacja, w której obsługujesz normalną sytuację przez exceptiony prowadzi do powstawania bardzo dużych plików z mapowaniem Exception -> kod błędu (a czasem i komunikat błędu), a to już jest sytuacja syfiasta.

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