Skip to content

remodov/hexagonal-architecture

Repository files navigation

Hexagonal Architecture

Аннотации и ArchUnit-правила для гексагональной архитектуры в Java-проектах.

Модули

  • hexagonal-architecture-core — аннотации (zero dependencies)
  • hexagonal-architecture-test — готовые ArchUnit-правила

Подключение

dependencies {
    implementation("ru.vikulinva:hexagonal-architecture-core:1.0.0")
    testImplementation("ru.vikulinva:hexagonal-architecture-test:1.0.0")
}

Аннотации

Аннотация Назначение Где применять
@InboundAdapter REST-контроллеры, gRPC, Kafka-консьюмеры Классы в adapter-слое
@OutboundAdapter JPA-репозитории, HTTP-клиенты, Kafka-продюсеры Классы в adapter-слое
@InboundPort Use case интерфейсы Интерфейсы/классы в application-слое
@OutboundPort Интерфейсы для внешних систем Только интерфейсы в application-слое

Пример

// Domain — чистая бизнес-логика
public class Order extends AggregateRoot<OrderId> { ... }

// Application layer — порты
@InboundPort
public class PlaceOrderUseCase {
    private final OrderPersistencePort persistencePort;

    public void execute(String orderId) {
        Order order = persistencePort.findById(orderId).orElseThrow();
        order.place();
        persistencePort.save(order);
    }
}

@OutboundPort
public interface OrderPersistencePort {
    Optional<Order> findById(String id);
    Order save(Order order);
}

// Adapter layer — реализации
@InboundAdapter("REST controller for orders")
@RestController
public class OrderController {
    private final PlaceOrderUseCase placeOrder;

    @PostMapping("/orders/{id}/place")
    public void placeOrder(@PathVariable String id) {
        placeOrder.execute(id);
    }
}

@OutboundAdapter("jOOQ-based order persistence")
@Repository
public class JooqOrderRepository implements OrderPersistencePort {

    private final DSLContext dsl;

    @Override
    public Optional<Order> findById(String id) {
        return dsl.selectFrom(ORDERS)
                .where(ORDERS.ID.eq(id))
                .fetchOptional(this::toOrder);
    }

    @Override
    public Order save(Order order) { ... }
}

Проверка архитектуры

Один тест для проверки всех правил:

import org.junit.jupiter.api.Test;
import ru.vikulinva.hexagonal.test.HexagonalArchitectureRules;

class ArchitectureTest {

    @Test
    void hexagonalArchitecture() {
        HexagonalArchitectureRules.verify(
            "com.example.myservice",
            "com.example.myservice.domain",
            "com.example.myservice.app",
            "com.example.myservice.adapter"
        );
    }
}

Проверяемые правила

  1. Домен не зависит от фреймворков — Spring, JPA, jOOQ, Kafka, Hibernate, gRPC, MongoDB, Redis, Elasticsearch, AWS SDK
  2. Домен не зависит от адаптеров
  3. Домен не зависит от application layer
  4. Application layer не зависит от адаптеров — направление: adapter → application → domain
  5. Inbound-адаптеры не зависят от outbound — и наоборот
  6. @OutboundPort только на интерфейсах
  7. @InboundAdapter/@OutboundAdapter не в domain-пакете

Выборочные правила

@Test
void domainIsPure() {
    JavaClasses classes = new ClassFileImporter()
        .importPackages("com.example.myservice");

    HexagonalArchitectureRules
        .domainMustNotDependOnFrameworks("com.example.myservice.domain")
        .check(classes);
}

Все доступные правила

HexagonalArchitectureRules.domainMustNotDependOnFrameworks(domainPkg)
HexagonalArchitectureRules.domainMustNotDependOnAdapters(domainPkg, adapterPkg)
HexagonalArchitectureRules.domainMustNotDependOnApplication(domainPkg, appPkg)
HexagonalArchitectureRules.applicationMustNotDependOnAdapters(appPkg, adapterPkg)
HexagonalArchitectureRules.inboundAdaptersMustNotDependOnOutboundAdapters(adapterPkg)
HexagonalArchitectureRules.outboundAdaptersMustNotDependOnInboundAdapters(adapterPkg)
HexagonalArchitectureRules.outboundPortMustBeInterface()
HexagonalArchitectureRules.adapterAnnotationsMustNotBeInDomain(domainPkg)

Рекомендуемая структура пакетов

com.example.myservice/
├── domain/            # Entity, ValueObject, AggregateRoot, DomainEvent
│   ├── model/
│   └── event/
├── app/               # Use cases, порты
│   ├── port/in/       # @InboundPort
│   └── port/out/      # @OutboundPort
└── adapter/           # Инфраструктура
    ├── in/            # @InboundAdapter — REST, gRPC, Kafka consumers
    │   ├── rest/
    │   └── grpc/
    └── out/           # @OutboundAdapter — JPA, HTTP clients, Kafka producers
        ├── persistence/
        └── client/

Совместимость

  • Java 21+
  • ArchUnit 1.4+ (для test-модуля)
  • Совместим с ddd-building-blocks и usecase-pattern

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages