Уровень зрелости 4: Инфраструктура изолирована
Четвёртый уровень Use Case Pattern: бизнес-логика и инфраструктура разнесены через порты и адаптеры. Один UseCase вызывается из REST, очереди и cron без дублирования.
← назад к методологии · уровень 4 из 4 · предыдущий: уровень 3
Какую проблему решает
На третьем уровне у вас уже есть домен, агрегаты, события — бизнес-правила в порядке. Но инфраструктура всё ещё перемешана с логикой: репозиторий импортирует jOOQ, handler знает про Spring, контроллер тащит в UseCase HttpServletRequest. Когда инфраструктура меняется (новый платёжный шлюз, переезд на Kafka, замена БД) — приходится править много мест.
Четвёртый уровень делает простую вещь: бизнес-логика не должна знать, в каком окружении она работает. Между ней и инфраструктурой проводится жёсткая граница — порты и адаптеры.
Когда подходит
- Десятки внешних интеграций — платёжные шлюзы, логистика, антифрод, сертификаты, налоги. Каждая со своим SDK, квирками, периодическими отказами.
- Один и тот же UseCase нужно вызывать из разных каналов: REST, обработчика очереди, cron, тестов — без дублирования.
- Долгоживущий продукт (5+ лет), бизнес-логика стабильнее инфраструктуры.
- Команда хочет менять адаптеры без страха уронить домен — например, мигрировать с Kafka на NATS, или с PostgreSQL на CockroachDB.
- Тесты бизнес-логики должны быть быстрыми и не зависеть от Spring-контекста.
Что должно быть
Жёсткое разделение пакетов. Корень проекта делится на два больших мира:
core/<bounded-context>/
domain/ ← агрегаты, value objects, события (то, что было на уровне 3)
usecase/ ← UseCase + Handler (бизнес-логика)
port/ ← интерфейсы для внешнего мира — порты
adapter/
in/rest/ ← REST-контроллеры
in/kafka/ ← обработчики очередей
in/scheduler/ ← cron-задачи
out/postgres/ ← реализации репозиториев и портов на jOOQ
out/payment/ ← обёртка над платёжным шлюзом
out/... ← другие внешние сервисы
Зависимости направлены строго внутрь.
core/ импортирует только Java-стандарт + методологические библиотеки (usecase-pattern, ddd-building-blocks, hexagonal-architecture-core). Никаких org.springframework.web.*, org.jooq.*, org.apache.kafka.* в core. Это проверяется автоматически — HexagonalArchitectureRules.verify(...) из hexagonal-architecture-test.
Все внешние взаимодействия — через порты в core/<bc>/port/.
Платёжный шлюз — PaymentGateway интерфейс в core, реализация в adapter/out/payment/. Шина событий — DomainEventPublisher (уже есть в ddd-building-blocks), реализация в adapter/out/kafka/. Хранилище — OrderRepository интерфейс в core, реализация в adapter/out/postgres/.
Один UseCase — несколько входных адаптеров.
CreateOrderUseCase может вызываться из REST-контроллера, из обработчика Kafka-сообщения и из cron-задачи. Все три места — это входные адаптеры. Сам UseCase ничего о них не знает.
Тесты бизнес-логики — без Spring.
Handler-тест: создал в памяти fake-реализации портов (или mock), вызвал handler.handle(useCase), проверил эффекты. Никакого @SpringBootTest, никакого Testcontainers, никакого 30-секундного старта.
Как проверять автоматически
Чтобы граница не «протекала», правила нужно закрепить в CI:
- ArchUnit-тест: «классы из
core.**не импортируютorg.springframework.**,org.jooq.**,org.apache.kafka.**». - ArchUnit-тест: «реализации портов лежат только в
adapter.out.**». - ArchUnit-тест: «контроллеры и обработчики очередей не вызывают друг друга — только UseCase через диспетчер».
Эти 3–5 правил отлавливают 90% попыток вернуть «удобный, но грязный» импорт.
Что должно быть зафиксировано в спеке
Спека по-прежнему Tier C, но в разделе «Интеграции (Context Mapping)» добавляются:
- список входных и выходных портов;
- какие адаптеры реализуют каждый порт;
- типы Context Mapping для пар: ACL, Open Host Service, Conformist, Customer/Supplier.
Стоимость
Уровень 4 не бесплатный. Он добавляет:
- 2× больше пакетов — на каждый порт интерфейс + хотя бы одну реализацию.
- Дополнительные ArchUnit-тесты в CI и культуру их поддерживать.
- Дисциплина в обзорах — нельзя «срочно дописать sql-запрос прямо в handler-е, потом перенесём».
Если у проекта мало интеграций и горизонт жизни короткий — четвёртый уровень принесёт боли больше, чем пользы. Это не «продвинутый = лучший». Это «нужный, когда есть причина».
Библиотеки и инструменты
| Что | Чем |
|---|---|
| UseCase + Handler | usecase-pattern-starter |
| Доменные абстракции | ddd-building-blocks |
| Аннотации портов/адаптеров + ArchUnit-правила | hexagonal-architecture (@InboundPort / @OutboundPort / @InboundAdapter / @OutboundAdapter + готовый HexagonalArchitectureRules.verify(...)) |
| Маппинг JsonBean ↔ домен | MapStruct |
| Готовые рецепты для адаптеров отказоустойчивости | Resilience4j |
| Проверка контрактов с соседями | OpenAPI / AsyncAPI + Pact для consumer-driven |
| AI-агенты | скиллы ucp-pattern-review проверяют, что граница не протекла |
Что дальше
Дальше идти некуда — это последний уровень. Но это не финал, потому что:
- В одном сервисе разные модули могут жить на разных уровнях. Ядро бизнеса на 4-м, справочники — на 1-м. Это нормально.
- Уровень 4 хорошо ложится на модульный монолит: те же
core/+adapter/модули, упакованные в один деплой. Когда нагрузка вырастет — выделение микросервиса не требует переписывания, только сборка отдельного артефакта.
Что почитать рядом
- Гексагональная архитектура — детальный разбор паттерна.
- Структурные паттерны микросервисов — API Gateway, BFF, ACL, Service Mesh, Strangler Fig.
- Распределённые паттерны — Outbox, Saga, Idempotent Consumer.
- Паттерны отказоустойчивости — что должно быть в адаптере выходного порта.
- Выбор начальной архитектуры — модульный монолит vs микросервисы.