← назад к методологии · уровень 2 из 3 · предыдущий: уровень 1
Какую проблему решает
На Уровне 1 бизнес-операции не выделены явно: они растворены в сервис-классах с десятками методов. Через полгода непонятно, что сервис умеет, проверки расходятся между вызовами, метрики и аудит прикручиваются руками.
Уровень 2 вводит одну дисциплину: каждую бизнес-операцию выделить в отдельный UseCase + Handler. Этого достаточно, чтобы не утонуть в сервис-классах и получить единый список операций сервиса.
Когда подходит
- Сервис с заметным числом операций, который будет жить и развиваться.
- Нужны автоматические метрики, аудит и единая обработка ошибок на каждую операцию.
- Хочется тестировать бизнес-операцию изолированно, без web-слоя.
- Команда работает по Use Case спецификации — операции из спеки ложатся в
UseCaseодин-в-один.
Что должно быть
Каждая бизнес-операция — отдельный UseCase + Handler.
Создание заказа — один UseCase. Получение заказа по id — другой. Не «один OrderService с пятью методами» — пять отдельных пар.
Один общий маршрут на все операции.
Контроллер не знает, какой обработчик вызовется — отдаёт UseCase в диспетчер, тот находит handler по типу. Диспетчер встроен в библиотеку.
Одна модель данных (по умолчанию). То, что приходит из API, и то, что лежит в БД, — одно и то же или связано простым маппингом. Отдельные доменные модели и Value Object — это Уровень 3.
Транзакция на уровне Handler. Один @Transactional на handler — всё или ничего.
Метрики на каждый UseCase автоматически — сколько раз вызвана операция, сколько падала, как долго работала. Этим занимается библиотека.
Как это выглядит
- Каждая операция — два класса:
<Operation>UseCase(record с входными полями) и<Operation>UseCaseHandler(методhandle+ аннотация Spring-компонента). - Контроллер: распарсил запрос → передал в диспетчер → завернул ответ.
- Бизнес-проверки и обращения к БД — внутри handler, нигде больше.
- Тесты — на handler, без mock-инфраструктуры контроллера и репозитория.
CQRS — опция этого уровня
CQRS (Command Query Responsibility Segregation) — не отдельный уровень зрелости, а опция Уровня 2. Включается, когда чтение и запись начинают мешать друг другу: длинные SELECT блокируют, запросам нужен агрессивный кэш, а записям — строгие транзакции.
Явное разделение по типу операции. Команды реализуют маркер UseCaseCommand (меняют состояние), запросы — UseCaseQuery (только читают). Это видно в типе и проверяется в обзоре.
Read Model — отдельная модель данных для чтения. Запросы идут не к той же таблице, что команды, а к оптимизированному представлению: материализованное view, denormalized-таблица, проекция в Elasticsearch, кэш в Redis.
Разные политики на handler-ах. Командные — @Transactional (read-write). Query — @Transactional(readOnly = true) или без транзакции, плюс @Cacheable.
Команды не возвращают «толстые» DTO — идентификатор, минимальный summary или UseCaseEmptyResult. За полными данными клиент идёт отдельным запросом, иначе смысл CQRS теряется.
Read Model строится из write-side — обычно через события (Outbox + relay), реже через материализованные view или ленивый rebuild. Это даёт eventual consistency: запрос может вернуть слегка устаревшие данные — фиксируйте это в спеке («UI видит изменения с задержкой до N секунд»).
Что НЕ нужно делать на этом уровне
- Заводить агрегаты, value objects и доменные события — это Уровень 3.
- Строить порты и адаптеры (
core/adapter) — тоже Уровень 3. - Использовать Event Sourcing — отдельная сложность, не путать с CQRS.
Библиотеки и инструменты
| Что | Чем |
|---|---|
| Каркас UseCase + Handler + Dispatcher | usecase-pattern-starter |
Маркеры UseCaseCommand / UseCaseQuery (CQRS) | usecase-pattern-starter (пакет ru.vikulinva.usecase.cqrs) |
| Web-слой | Spring Boot, OpenAPI Codegen |
| Авторизация | Spring Security + OAuth2 |
| Хранилище | jOOQ или JPA |
| Read Model | материализованные view PostgreSQL, Redis, Elasticsearch |
| Маппинг моделей (при CQRS) | MapStruct |
| Метрики | Micrometer (теги usecase.kind=command\|query) |
Признаки, что пора на уровень 3
- Бизнес-правил много, и они расползаются по handler-ам — проверки копипастятся.
- В команде заводят разные термины для одного и того же — нужен общий язык домена.
- Появляются явные инварианты («нельзя оплатить неподтверждённый заказ», «остаток не может быть отрицательным»), и хочется, чтобы они жили в одном месте.
- Модули логически самодостаточны — намечаются Bounded Context-ы.
Тогда — Уровень 3: DDD + Hexagonal.
Что почитать рядом
- CQRS — разделение чтения и записи как отдельный паттерн.
- Use Case Pattern (методология) — все три уровня в одном месте.
- Apache Kafka — если Read Model обновляется через события.
- Распределённые паттерны — Outbox для атомарной публикации, Idempotent Consumer для приёма.