В моем предыдущем сообщении я показал, как реализовать механизм обработки исключений домена, который совместим со способом обработки исключений в 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?

Свяжитесь с нами!

Мы заставим технологии работать на ваш бизнес. Посмотреть проекты, которые мы успешно реализовали.