Как обработать в Spring приложении ситуацию с датой начала договора меньше даты окончания
Что проверяют
Знаете ли вы, что проверка одного поля и проверка связи двух полей делаются по-разному, и куда в Spring-приложении кладётся такое правило. Следом спрашивают, какой HTTP-статус получит клиент и что будет, если договор придёт не через REST, а из очереди.
Как отвечать
Правило «начало не позже окончания» касается двух полей, поэтому аннотации на поле вроде @NotNull или @FutureOrPresent его не выразят. Нужна проверка уровня класса:
- своя аннотация:
@ValidPeriodна DTO иConstraintValidator<ValidPeriod, ContractRequest>, вisValidсравнение!start.isAfter(end). ЧерезbuildConstraintViolationWithTemplate(...).addPropertyNode("endDate")ошибку можно привязать к конкретному полю; - быстрый вариант: метод
@AssertTrue boolean isPeriodValid()прямо в DTO, без отдельного класса; - контроллер:
@Valid @RequestBody ContractRequest request. При нарушении Spring броситMethodArgumentNotValidException, и клиент получит 400. Нуженspring-boot-starter-validation, пакеты в Spring Boot 3 ужеjakarta.validation; - формат ошибки:
@RestControllerAdviceприводит исключения к единому ответу, удобно вProblemDetail(RFC 9457), который появился в Spring Framework 6.
Валидатор должен возвращать true, если одна из дат null, иначе на пустом поле он упадёт с NullPointerException. Обязательность проверяют отдельные @NotNull. Уточните и то, допустимы ли равные даты.
Одной валидации DTO мало. Договор может прийти из Kafka или пакетной загрузкой, поэтому инвариант дублируют в доменной модели (конструктор или фабрика Contract) и в базе ограничением CHECK (start_date <= end_date) в миграции Flyway или Liquibase.
Где спотыкаются
Бросают IllegalArgumentException из сервиса без обработчика. Spring MVC превращает необработанное исключение в 500, и ошибка клиента выглядит как падение сервера.
Вызывают LocalDate.now() прямо в проверках «дата не в прошлом». Результат зависит от часового пояса сервера, тест становится нестабильным, лучше внедрить Clock.
Ставят @Validated на сервис и считают, что даты проверяются. Без аннотаций ограничений на параметрах метода она ничего не делает.