Open-source Java-библиотека для пометки и автоматической проверки границ гексагональной архитектуры.

github.com/remodov/hexagonal-architecture

Теория и обоснование, зачем нужны порты и адаптеры — в отдельной статье Гексагональная архитектура. Эта страница — про библиотеку: что она даёт, как подключить, как настроить проверку.

Что внутри

Два модуля:

  • hexagonal-architecture-annotations — аннотации @Port, @Adapter, @UseCase, @DomainModel для разметки классов и пакетов. Без рантайма, без зависимостей кроме чистой Java.
  • hexagonal-architecture-archunit — ArchUnit-правила, которые на каждом запуске тестов проверяют, что:
    • доменный слой не зависит от Spring, JPA, Kafka и других инфраструктурных библиотек;
    • адаптер реализует ровно один порт;
    • use case вызывается только из адаптеров входа, не из доменных классов;
    • аннотация @DomainModel стоит на классах в пакете domain.*, и наоборот — все классы в domain.* должны быть помечены.

Подключение

// build.gradle.kts
dependencies {
    implementation("ru.vikulinva:hexagonal-architecture-annotations:1.0.0")
    testImplementation("ru.vikulinva:hexagonal-architecture-archunit:1.0.0")
    testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
}

Разметка модулей

В типовом Gradle-multi-module-проекте Order Service структура такая:

order-service/
  domain/                       — @DomainModel: чистая Java, Entity, VO, агрегат
  application/                  — @UseCase: оркестрация, транзакции, доменные события
  adapter-rest/                 — @Adapter: REST-контроллеры (адаптер входа)
  adapter-postgres/             — @Adapter: jOOQ-репозитории (адаптер выхода)
  adapter-kafka/                — @Adapter: KafkaListener + KafkaProducer
  port-orders/                  — @Port: контракты, реализуемые adapter-*

На уровне пакетов:

@DomainModel
package com.example.order.domain;

import ru.vikulinva.hexagonal.DomainModel;
@UseCase("CreateOrder")
public class CreateOrderUseCase {
    private final OrderRepository orders;          // порт
    private final OrderEventPublisher events;      // порт
    // ...
}
@Port
public interface OrderRepository {
    Order findById(OrderId id);
    void save(Order order);
}
@Adapter
public class JooqOrderRepository implements OrderRepository {
    // jOOQ-имплементация — здесь живёт всё, что знает про PostgreSQL
}

Проверка на CI

В src/test/java/.../ArchitectureRulesTest.java:

@AnalyzeClasses(packages = "com.example.order")
class ArchitectureRulesTest {

    @ArchTest
    static final ArchRule domain_isolated =
        HexagonalRules.domainHasNoInfrastructureDependencies();

    @ArchTest
    static final ArchRule adapters_implement_ports =
        HexagonalRules.adaptersImplementSinglePort();

    @ArchTest
    static final ArchRule use_cases_called_from_inbound_adapters =
        HexagonalRules.useCasesCalledFromInboundAdaptersOnly();

    @ArchTest
    static final ArchRule domain_classes_annotated =
        HexagonalRules.allDomainPackagesAnnotated();
}

Прогон на каждом PR — четыре правила, нарушение каждого ловится с конкретным указанием класса.

Когда применять, когда нет

Библиотека соответствует 4-му уровню зрелости Use Case Pattern. Это сильный инструмент, но не бесплатный — требует разделения на Gradle-модули, дисциплины с импортами, дополнительных типов в коде. Брать стоит:

  • на сервисах с большой долей бизнес-логики (Order, Payment, Reservation) — там, где есть инварианты домена и большая стоимость их нарушения;
  • когда команда хочет проверять архитектурные правила на CI, а не в код-ревью;
  • когда планируется замена инфраструктуры (например, jOOQ → Spring Data, REST → gRPC) без переписывания доменного слоя.

Не брать на:

  • CRUD-сервисах и справочниках — там оверхед не окупается;
  • сервисах из 2–3 use case'ов — однострочный сервис без порта читается легче, чем тот же код с тремя аннотациями.

Подробное объяснение, какие архитектурные проблемы решает Ports & Adapters и как сравнивать с классической слоёной архитектурой — в статье Гексагональная архитектура.

Лицензия

Apache 2.0 — можно использовать в коммерческих проектах без ограничений.