В моем предыдущем сообщении я показал, как реализовать механизм обработки исключений домена, который совместим со способом обработки исключений в Spring Framework. Работает плавно, но одна важная с точки зрения потребителей API вещь раздражает. При обработке входящего запроса приложение возвращает исключение, содержащее подробную информацию о первой обнаруженной ошибке. Вместо этого все ошибки должны быть объединены и возвращены сразу. Посмотрим, что с этим можно сделать.
Краткое резюме
Мне был нужен механизм обработки исключений, который бы отвечал следующим требованиям:
# 1 Совместимость с форматом сообщений об исключениях Spring.
# 2 Автоматический перевод всех исключений, возникающих в приложении, в соответствующие коды состояния HTTP.
# 3 Все исключения должны наследоваться от одного и того же базового класса.
# 4 Должен быть единый централизованный пункт, где обрабатываются все исключения.
# 5 Дополнительное поле, а именно traceId
, должно быть добавлено ко всем исключениям, которые перехватываются и возвращаются потребителю API.
Могу я узнать код?
Есть специальный репозиторий со всеми примерами для этого сообщения в блоге.
Механизм реализован путем расширения DefaultErrorAttributes
и перезаписи метода getErrorAttributes()
.
Вместе с классом DomainExceptionWrapper
идет DomainException
:
что также является корнем всей иерархии исключений домена.
Агрегация ошибок домена
Предположим, что во входящем запросе все поля установлены согласно JSR-303 / JSR-348 Bean Validation - это означает, что все обязательные поля установлены, нет неожиданных значений null
, поля String
соответствуют регулярному выражению, а поля Collection
соответствуют ограничение @Size
. Теперь пришло время для проверки домена, которая обычно не может быть выполнена на веб-уровне, поскольку может потребоваться доступ к другим ресурсам. В нашем примере AddressController
принимает запросы на создание адреса и имеет связанный с ним NewAddressValidator
. Валидатор проверяет две вещи:
- мы не принимаем адреса из Молвании
- почтовый индекс и провинция из запроса должны совпадать
Как я сказал в начале, для потребителя API было бы неприятно получать все ошибки одну за другой. Вместо этого они хотели бы получить все агрегированные ошибки, исправить их, отправить запрос еще раз и получить успешный ответ. Наш предыдущий механизм обработки исключений его не поддерживает. И .. Должен! Конечно, он совместим со Spring :).
BindingResult
В классе DefaultErrorAttributes
в ответ добавляются ошибки, если обрабатываемое исключение реализует интерфейс BindingResult
. Итак, что нам нужно сделать, это добавить BindingResult
в нашу иерархию исключений. Вам решать, хотите ли вы, чтобы каждое исключение имело набор DomainError
. Я не считаю это обязательным, поэтому я определил класс DomainExceptionWithErrors
:
от которых могут наследоваться исключения, представляющие HTTP 422 Unprocessable entity
или HTTP 400 Bad request
. DomainExceptionWithErrors
также реализует HasDomainErrors
, который, в свою очередь, расширяет уже упомянутый интерфейс BindingResult
. Причина этого - многословие BindingResult
, и я не хотел, чтобы он загромождал остальную часть иерархии всеми методами, которые он определяет. Вместо этого в HasDomainErrors
я определяю единственный, не используемый по умолчанию getBindingResult()
метод, который должен быть реализован в определенном классе, и предоставляю экземпляр объекта BindingResult
, где все вызовы будут делегированы. В DomainExceptionWithErrors,
экземпляр MapBindingResult
- который является одной изBindingResult
реализаций, предоставляемых Spring - играет эту роль. Таким образом, я могу легко добавить все ошибки проверки домена в исключение, которое будет сгенерировано.
Чтобы лучше понять, что здесь происходит, взгляните на тесты, которые я подготовил специально для демонстрации.
Что мне не нравится ...
... об этой реализации заключается в том, что она протекает. Как я упоминал ранее, BindingResult
- это подробный интерфейс (40 методов!), И при использовании автозавершения кода в вашей среде IDE может потребоваться некоторое заметное время, чтобы найти метод, задача которого заключается в добавлении экземпляра DomainError
. Попытка помочь с этим неудобством состоит в том, чтобы добавить несколько методов, предназначенных для добавления DomainError
в исключение. Может можно лучше сделать? Если да, поделитесь, пожалуйста, своими идеями!
Ищете экспертов по Scala и Java?
Мы заставим технологии работать на ваш бизнес. Посмотреть проекты, которые мы успешно реализовали.