REST API powinno uwzględniać obsługę wyjątków w przypadku kiedy dany zasób nie istnieje, lub pojawi się inny mniej oczekiwany wyjątek. W tym materiale pokazuje jak zaimplementować obsługę wyjątków przy wykorzystaniu AOP – co pozwoli jednocześnie odseparować logikę obsługi błędu.
Odseparowanie logiki wyjątku
Dobrym krokiem pozwalającym na oddzielenie logiki wykonywanej przez REST API od obsługi błędu jest wykorzystanie AOP. AOP, czyli programowanie zorientowane aspektowo pozwala przenieść działanie np. wyjątku do odrębnych klas. W ten sposób nie zaśmiecamy metod webowych i otrzymujemy czysty kod. W związku z tym przypomnienie jak działa AOP znajdziesz w artykule na temat programowania zorientowanego aspektowo.
Kiedy pojawia się problem?
Rozpatrzmy przykład księgarni – użytkownik chce pobrać książkę na podstawie id. Naiwna implementacja wygląda następująco:
@GetMapping("/book/{id}") public Book getBook(@PathVariable long id) { return bookRepo.findById(id).get(); }
Problem pojawia się w momencie, kiedy użytkownik odwołuje się do elementu na liście, którego nie ma. Wówczas aplikacja zwróci mało precyzyjny błąd + najprawdopodobniej błędny kod odpowiedzi -> 500 zamiast 404. Dlatego jako programiści powinniśmy zadbać o prawidłowe zwracanie statusów odpowiedzi.
Obsługa wyjątków w REST API – implementacja
Po pierwsze dobrze jest wykorzystać metodę orElseThrow na obiekcie pobieranym z repozytorium. Pozwala ona na wywołanie wyjątku w przypadku kiedy dany element nie zostanie odnaleziony.
@GetMapping("/book/{id}") public Book getBook(@PathVariable long id) { return bookRepo.findById(id).orElseThrow(() -> new BookNotFoundException(id)); }
W ramach konstruktora BookNotFoundException możemy przekazać dowolna wartość, jednak dobrą praktyką jest przekazanie bezpośrednio informacji na temat elementu, który stał się przyczyną wywołania wyjątku – po to, aby można było ująć go w komunikacie.
Dlatego w ten sposób, zaimplementowałem nową klasę wyjątku z jasną informacją dla klienta:
public class BookNotFoundException extends RuntimeException { public BookNotFoundException(long id) { super("Could not find book: " + id); } }
Przechwytywanie obsługi wyjątku
Ostatnim elementem jest przechwycenie obsługi wyjątku przez AOP. Z tego powodu tworzymy handler, który uruchomi się w momencie rzucenia wyjątku i to on będzie dawał odpowiedź klientowi.
Dlatego też posiada adnotacje:
- @ResponseBody – pozwalająca na przemapowanie formatu na JSON/XML;
- @ExceptionHandler(BookNotFoundException.class) – która pozwala nasłuchiwanie, kiedy pojawi się podany wyjątek i wówczas podjąć inicjatywę;
- @ResponseStatus(HttpStatus.NOT_FOUND) – tak, aby status kod odpowiedzi był właściwy;
Cała klasa obsługi błędu wygląda następująco:
@ControllerAdvice public class BookNotFoundAdvice { @ResponseBody @ExceptionHandler(BookNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public String bookNotFoundHandler(BookNotFoundException ex) { return ex.getMessage(); } }
Adnotacja @RestControllerAdvice
Klasa, która posiada tylko porady, które zwracają informacje do klienta może przyjąć uproszczenie adnotacyjne. Zamiast nad każdą metodom pisać osobno @ResponseBody, to można wykorzystać nad klasą adnotacje @RestControllerAdvice, która już w sobie zawiera wyżej wspominaną adnotacje. Wówczas możemy wykorzystać zapis uproszczony:
@RestControllerAdvice public class BookNotFoundAdvice { @ExceptionHandler(BookNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public String bookNotFoundHandler(BookNotFoundException ex) { return ex.getMessage(); } }
Inne rodzaje błędów i typ zwracany ResponseEntity
W innym artykule opisałem wykorzystanie ResponseEntityExceptionHandler, który w połączeniu z ResponeEntity prezentuje jak można rozbudować omawiany przypadek. Dlatego zachęcam Cię również do jego przeczytania przy okazji uzupełniania wiedzy z tego zakresu.
Obsługa wyjątków w REST API – materiał wideo
Jeśli preferujesz formę wideo, gdzie pokazuje i omawiam w jaki sposób dokonać obsługi wyjątków w REST API, to zapraszam Cię do mojego materiału wideo:
Obsługa wyjątków w REST API – kod źródłowy
Kod do pobrania znajduje się na GitHubie. Enjoy 🙂