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, H2, Linux, Hibernate, Collections, Stream API, многопоточность, чат-боты, нейросети, файлы, devops, Docker, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.