Hej
chciałbym się podzielić swoimi przemyśleniami i popytać was o opinie, jak wy to widzicie.
Ostatnio siadłem do tematu controllerów u nas w aplikacji, zastanawiałem się w jaki sposób ładnie obsługiwać błędy.
Projekt został tworzony od zera więc przepchaliśmy Eithera przez wszystkie warstwy aby zapewnić dobrą obsługę błedów.
Na warstwie controllerów dostajemy eithera, jeżeli dostajemy Issue rzucamy wyjątek które jest łapany w ControllerAdvice. Trochę mi się to nie podoba, nie przepadam nad rzucaniem wyjątków i łapaniem go gdzieś wyżej np ApiErrorHandler. Coraz większa ilość @ExceptionHandler zachęciła nas do zmiany obsługi błedów więc postanowiliśmy stworzyć klase pomocniczą :
class SuccessOrError <T, K extends ErrorResponse>>
przykładowa metoda kontrolera
public ResponseEntity<SuccessOrError<JakaśDataDto, ErrorDto>>
Serializacja też ogarnięta. Fabryka dla ErrorDto przyjmuje w parametrze Issue<?> który pozwala na zbudowanie odpowiedniej odpowiedzi.
Fabryka również ma dostarczoną listę typów Issuesów z których w razie wystąpienia ma wyciągnąć message i zwrócić użytkownikowi.
Np chcemy powiedzieć użytkownikowi że:
{
"type": "CREATE_TEMPLATE_ERROR",
"message": "Nie udało się utworzyć szablonu."
"Caused by": [
"Boś głupi",
"Zła wartość dla pola: mahmol",
"Nierozpoznany atrybut: bbebrre"
]
}
I jeżeli wszystko dobrze to :
{
"template":[...]
}
Sam issue pozwala na wyszukanie root messegów dla zdefiniowanego typu
public interface Issue<T extends Enum<?>>
{
String getMessage();
T getIssueType();
Optional<Exception> getRootCause();
Set<? extends Issue<?>> getRootIssue();
default Set<Issue<?>> findRootIssuesByTypes(Set<Enum<?>> issueTypes)
Ogólnie taki sposób mi się podoba, bo Issuesy w core rzucają błąd, nic ich nie obchodzi. Każda warstwa wyżej tworzy swój Issue z informacją czego nie udało się zrobić w jej warstwie wraz z informacją z dołu. Powstaje wtedy graf przeważnie z pojedynczymi wiązaniami (Wiele wiązań w przypadku gdy wiemy że użytkownik podał kilka błędnych atrybutów, zwracamy wtedy informacje o wszystkich złych):
- Nie udało się edytować szablonu *1 (API)
-> Nie można wczytać szablonu (CORE)
-> Wystąpił błąd ładowania atrybutów *2 (CORE)
-> Nie można odczytać atrybutu dla cośtamcośtam (CORE)
-> Błąd odczytania pliku: ścieżka, Plik nie istnieje (INFRA)
Wtedy na warstwie api możemy decydować którą informację możemy wyświetlić użytkownikowi np *1 i *2 , jednocześnie logująć cały stack i informacje krok po kroku co się wydarzyło.
Co o tym myślicie ? Jakie znacie fajne sposoby obsługi błędów ?