19 марта 2023
Тэги: gradle, json, Kotlin, rest, Spring Boot, YouTube, руководство.
В предыдущей статье Spring Data JPA, REST и Kotlin: создание, обновление, удаление мы научились изменять данные в базе с помощью Spring Data JPA. При обновлении и удалении мы сначала проверяем, что запись существует. И если она не найдена – кидаем стандартное исключение. Обеспечивается это поведение через элвис-оператор:
Если возникает данная ошибка, то клиент в ответ получит json примерно следующего содержания:
Такой формат идёт в Spring Boot «из коробки». Здесь есть какая-то второстепенная информация, но при этом нет самого главного – причины ошибки. Кроме того, у каждого типа ошибки может быть своя дополнительная информация, которая облегчает диагностику проблемы. Конечно, с точки зрения безопасности не стоит возвращать детали внутренней реализации в ответе на запрос. Для этого существует логирование на сервере. Но возвращать хотя бы осмысленный код ошибки не помешает.
Давайте научимся менять стандартный формат ответа при ошибке в нашем rest API.
Создадим новый data class под названием ApiError.
Как видите, он содержит код ошибки и её описание.
Код ошибки чаще всего бывает числовым, но я предлагаю использовать текстовый код ошибки. Главное его преимущество перед числовым – тип ошибки понятен из названия. Нет нужды сверять код с таблицей кодов ошибок. При этом важно, что код ошибки – фиксированная и неизменяемая строка, чтобы можно было завязаться на её значение на клиентской стороне.
Мне нравится такой текстовый формат, в котором все слова записаны строчными буквами, а вместо пробелов используются точки. Но это дело вкуса.
Теперь можем создать базовый абстрактный класс BaseException для всех кастомных исключений в нашем приложении.
В него мы добавляем созданный нами ApiError, а также поле стандартного типа HttpStatus, т.к. у каждой ошибки в rest API должен быть свой статус HTTP. Например, если запись не найдена – принято возвращать статус 404.
Базовое исключение наследуем от RuntimeException, чтобы его не нужно было явно прописывать в сигнатурах метода. Это больше имеет смысла для взаимодействия с Java, т.к. в Kotlin все исключения unchecked. В конструктор RuntimeException передаем description – это уточняющее описание ошибки.
Теперь нужно создать глобальный обработчик ошибок ErrorHandler.
На этот обработчик вешаем аннотацию @ControllerAdvice. Также требуется унаследовать его от ResponseEntityExceptionHandler.
Затем в обработчике мы можем объявлять методы, которые обрабатывают специфические исключения благодаря аннотации @ExceptionHandler. Если мы укажем BaseException в этой аннотации, то метод будет обрабатывать исключения данного типа и его наследников в любом месте нашего приложения.
В качестве результата метод возвращает ResponseEntity, где первый параметр – это тело ответа, а второй – httpStatus. Всю эту информацию мы берём из нашего исключения.
Теперь создадим исключение бизнес-логики CountryNotFoundException.
Наследуем его от BaseException. В качестве http-кода указываем HttpStatus.NOT_FOUND (тот самый 404), а в ApiError указываем текстовый код ошибки и словесное описание с указанием id страны, которая не найдена.
Теперь в сервисном слое в CountryServiceImpl заменим все фрагменты кода с поиском страны и RuntimeException на созданное нами исключение:
Как видите, кастомное исключение использовать удобнее, потому что даже текст ошибки писать не нужно – это инкапсулировано в самом исключении. А значимые параметры ошибки передаются через конструктор.
Теперь, если страна не найдена, ответ будет выглядеть так:
Как видите, мы избавились от второстепенной информации. При этом возвращается текстовый код ошибки, который, во-первых, читаемый, а во-вторых, на него можно завязаться на клиентской стороне и реализовать дополнительную обработку.
В следующей статье Spring Data JPA, REST и Kotlin: "один-ко-многим", чтение данных мы создадим дочернюю сущность «Город» и будем возвращать информацию о двух связанных между собой сущностях.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.