Проверка ограничений в микросервисах Spring Boot

Легкое введение в проверку запросов с помощью аннотаций в Java

В микросервисной архитектуре службы могут принимать несколько, если не много, одних и тех же входных данных. Этот шаблон может легко привести к дублированию кода и избыточности между сервисами. Стремясь смягчить эти недостатки и сохранить фокус на сервисном коде, мы можем разработать надежное решение, включающее несколько API, предоставляемых Spring и Java.

Следующее руководство потребует некоторых практических знаний о Java и Spring Boot, но будет соответствовать разным уровням навыков. Тем не менее, никогда не помешает увидеть код другого разработчика!

Фон

Наше решение будет включать объединение API-интерфейсов Java и Spring Boot, ConstraintValidator и ResponseEntityExceptionHandler соответственно.

Hibernate Validator, расширенный как часть JSR 380, представляет собой спецификацию Java API для стандартной проверки bean-компонентов. В контексте приложений Spring Boot вы, возможно, использовали это недолго думая. Примеры включают:

  • @NotNull
  • @Min
  • @Max
  • @Pattern
  • @Past
  • @Email
  • @PositiveOrZero

В этом руководстве мы рассмотрим, как выйти за рамки этих базовых проверок, используя интерфейс ConstraintValidator для определения нашего собственного набора ограничений.

В то время как в экосистеме Spring существуют другие формы обработки исключений, ResponseEntityExceptionHandler обеспечивает глобальную (и централизованную) обработку исключений внутри службы. Эта глобализация является ключом к эффективности нашей аннотации пользовательских ограничений, поскольку она позволяет нам проверять несколько Bean-компонентов (или полей в них). Тем не менее, мы исследуем, как мы можем использовать этот класс, чтобы изящно обрабатывать нарушения наших ограничений.

Реализация

Давайте углубимся. Чтобы не перегружать руководство стандартным кодом, вы найдете только необходимые блоки кода в разделах ниже. К этой статье прилагается рабочий пример на GitHub.

Примечание. Я буду ссылаться на этот пример проекта на протяжении всего руководства.

Зависимости

Список короткий и приятный:

Создание аннотаций

Начнем с простого. Допустим, мы внедрили холодильник и кладовую, которые позволяют нам:

  • Управление репозиториями Fridge и Pantry
  • Принимать запросы POST и / или PUT с полезной нагрузкой JSON

Мы хотим проверить общие поля между моделями запросов обеих служб. Наша модель запроса может выглядеть примерно так:

Одно из простейших ограничений, которые мы можем создать, будет включать составление существующих ограничений, таких как @PositiveOrZero и @Max в приведенном выше примере. Это позволяет нам явно обозначать общие ограничения и называть это «бизнес-логикой». Ниже мы определяем @FoodQuantity:

Здесь много всего происходит, поэтому давайте разберемся:

  • @Constraint отмечает аннотацию как ограничение проверки компонента и позволяет нам указывать ConstraintValidator реализации; здесь приветствуются ноль, одна или несколько реализаций
  • @Retention настроен таким образом, что наша аннотация будет сохраняться во время выполнения
  • @Target настроен таким образом, что мы можем проверять различные типы входных данных для наших служб.
  • message, groups и payload требуются для @Constraint, но их необязательно устанавливать - они обеспечивают конкретность, выходящую за рамки того, что мы рассмотрим сегодня.

Это ни в коем случае не упрощение. Если не обращать внимания на многословие, аннотация открывает немало возможностей, чтобы упростить обработку сложности, как мы увидим в следующем примере.

Давайте определим ограничение для поля category таким образом, чтобы:

  • Категория должна быть передана и не может быть пустой
  • Допускаются только определенные категории
  • Категории могут отличаться для услуг Холодильник и Кладовая.

Чтобы реализовать эту аннотацию, мы расширим предпосылку нашей первой аннотации, добавив специальный параметр и предоставив реализацию интерфейса ConstraintValidator. Результат выглядит примерно так:

В этой аннотации происходит еще несколько вещей по сравнению с @FoodQuantity. Мы указали новый параметр allowed, чтобы ограничить то, что может быть передано в category. Обратите внимание на значение по умолчанию - на этот массив ссылаются только в том случае, если значения не передаются в @FoodCategory. Чтобы справиться с этим ограничением, мы реализовали FoodCategoryValidator:

Давайте разберем наш новый класс валидатора:

  • ConstraintValidator параметризуется классом аннотации и проверяемым типом - String, содержащий значение category
  • Глобальное поле allowed, установленное в переопределенном методе initialize; именно в этом методе мы получаем доступ к параметрам @FoodCategory для использования во всем классе валидатора
  • isValid - это основа нашей проверки ограничений
  • Для недопустимых сценариев мы отключаем нарушение ограничения по умолчанию, создаем правильное сообщение об ошибке и возвращаем false - это в конечном итоге вызывает исключение, которое нам будет интересно позже.

Наконец, чтобы получить максимальную отдачу от нашей аннотации, мы разделим поле категории на два подкласса, относящихся к каждой службе.

После всей нашей тяжелой работы мы получили чистый набор моделей запросов, готовых к засыпанию недопустимыми значениями:

Обработка ошибок валидации

До сих пор мы определяли только те ограничения, которые мы (и наши потребители) должны соблюдать. Давайте откроем конечную точку, чтобы можно было добавлять продукты в холодильник, и протестируем наши ограничения:

Чтобы наши аннотации работали правильно, необходимо обратить внимание на несколько важных аспектов:

  • @Validated должен использоваться на уровне класса или метода, чтобы указать, где должна происходить проверка.
  • @Valid используется, чтобы пометить свойство для каскадирования проверки, которое запускает наши ограничения

Отправим полезную нагрузку:

Успех! Но давайте посмотрим, что произойдет, когда мы отправим другую полезную нагрузку, которая, как мы знаем, приведет к ошибке:

Обратите внимание, что мы нарушаем несколько ограничений для этой модели запроса. Вы должны увидеть подробный ответ, содержащий подробную информацию об обнаруженных ошибках и созданных исключениях. Такая многословность не идеальна для нас (или наших потребителей), поэтому давайте отфильтруем важные детали, просто переопределив ResponseEntityExceptionHandler.

При дальнейшем рассмотрении ответа об ошибке, предоставленного Spring, вы можете заметить возникшее исключение: MethodArgumentNotValidException. Это исключение, которое нас будет интересовать при обработке нарушений ограничений в обработчике исключений.

Во-первых, нам нужна модель для сбора соответствующей информации. Из исходного ответа Spring мы можем извлечь следующее:

Затем мы создадим глобальный обработчик исключений и метод для обработки наших ограничений:

Отметим здесь несколько вещей:

  • @RestControllerAdvice именно это - специализированный компонент для классов, объявляющих @ExceptionHandler методы, общие для нескольких классов контроллеров.
  • Мы переопределяем метод @ResponseEntityExceptionHandler handleMethodArgumentNotValid, чтобы мы могли:
  • Регистрируйте важную информацию; построить небольшой, сфокусированный ответ на ошибку; и вернем код состояния HTTP по нашему выбору в зависимости от нарушенного ограничения

После отправки полезной нагрузки, которая нарушает определенные нами ограничения, вы должны увидеть краткий ответ, указывающий, в чем мы ошиблись:

Вызов

В этом руководстве представлены некоторые основные формы проверки ограничений в службе REST на основе Spring / Java. Если вы хотите пойти немного дальше, вот несколько мест, с которых вы можете начать:

  • Узнайте, что может отличаться при нарушении ограничений @PathVariable или @RequestParam
  • Реализуйте вложенные ограничения в сложной модели запроса
  • Повышение гибкости кода состояния HTTP, возвращаемого потребителю.
  • Разверните пример проекта, чтобы учесть нюансы составной службы - например, Picnic Service.
  • Изучите @Constraint API дальше - для чего можно использовать payload и groups?

Закрытие

На этом мы завершаем руководство по реализации настраиваемых валидаторов ограничений с использованием аннотаций! Не бойтесь сообщать мне, если я что-то пропустил. Я, конечно, приветствую (и ценю) критику, вопросы и тому подобное.

Для справки - репозиторий GitHub с полным рабочим кодом и примерами, представленными в этой статье.

Первоначально опубликовано на https://verley.dev.