Hexagonal Architecture (Ports & Adapters)

Version française

Complete guide to Hexagonal Architecture (Ports & Adapters): isolate business logic from infrastructure, improve testability, and build flexible systems that evolve with changing technical requirements.

Table of Contents

1. What is Hexagonal Architecture?

Hexagonal Architecture, also known as Ports and Adapters, was introduced by Alistair Cockburn. The goal is to place the application core (domain and use cases) at the center of the system and treat everything else—databases, UIs, message brokers, third-party APIs—as plug-in components connected through well-defined interfaces.

The name comes from the idea that the application can be approached from multiple sides (like the edges of a hexagon). Each side represents a different way the system interacts with the outside world: HTTP APIs, CLI, batch jobs, messaging, persistence, and more.

Unlike traditional layered architectures where dependencies often point inward toward infrastructure concerns, hexagonal architecture enforces dependency inversion: the domain defines what it needs (ports), and infrastructure provides implementations (adapters).

flowchart TB subgraph Inbound["Inbound Adapters"] REST[REST Controller] CLI[CLI / Scheduler] MSG_IN[Message Consumer] end subgraph Core["Application Core"] UC[Use Cases / Services] DOM[Domain Model] end subgraph Outbound["Outbound Adapters"] REPO[Database Repository] API[External API Client] MSG_OUT[Event Publisher] end REST --> UC CLI --> UC MSG_IN --> UC UC --> DOM UC --> REPO UC --> API UC --> MSG_OUT style Core fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style Inbound fill:#fff4e1,stroke:#f57c00,stroke-width:2px style Outbound fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

2. Why Use Ports and Adapters?

  • Testability: replace real databases or APIs with in-memory or mock adapters during unit and integration tests.
  • Technology independence: swap PostgreSQL for MongoDB, or REST for gRPC, without rewriting business rules.
  • Clear boundaries: ports document the contract between business logic and the outside world.
  • Evolvability: add new delivery mechanisms (mobile API, GraphQL, events) by adding adapters, not changing the core.
  • Alignment with DDD: pairs naturally with domain-driven design and clean architecture principles.

3. Core Concepts

3.1. Application Core

The core contains domain entities, value objects, domain services, and application services (use cases). It must not depend on frameworks, ORM annotations, or HTTP details.

3.2. Ports

A port is an interface that defines how the application communicates with the outside world.

  • Inbound ports (driving): APIs the application exposes to the outside (e.g., PlaceOrderUseCase).
  • Outbound ports (driven): abstractions the application needs from infrastructure (e.g., OrderRepository, PaymentGateway).

3.3. Adapters

An adapter implements a port for a specific technology. Inbound adapters call inbound ports; outbound adapters implement outbound ports.

4. Ports and Adapters in Practice

4.1. Inbound Adapter Example

A Spring REST controller translates HTTP requests into use case calls:

@RestController
@RequestMapping("/orders")
public class OrderController {
    private final PlaceOrderUseCase placeOrder;

    public OrderController(PlaceOrderUseCase placeOrder) {
        this.placeOrder = placeOrder;
    }

    @PostMapping
    public ResponseEntity<OrderResponse> create(@RequestBody CreateOrderRequest request) {
        OrderId id = placeOrder.execute(request.toCommand());
        return ResponseEntity.status(HttpStatus.CREATED).body(OrderResponse.from(id));
    }
}

4.2. Outbound Port and Adapter

// Outbound port (in application module)
public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId id);
}

// Outbound adapter (in infrastructure module)
@Repository
public class JpaOrderRepository implements OrderRepository {
  private final OrderJpaRepository jpa;

  @Override
  public void save(Order order) {
    jpa.save(OrderEntity.from(order));
  }

  @Override
  public Optional<Order> findById(OrderId id) {
    return jpa.findById(id.value()).map(OrderEntity::toDomain);
  }
}

5. Implementation Example

A typical modular layout in Java:

  • domain — entities, value objects, domain events
  • application — use cases, inbound/outbound port interfaces
  • infrastructure — JPA, REST, messaging adapters
  • bootstrap — Spring configuration, dependency wiring

Dependency rule: infrastructure → application → domain. The domain module has zero dependencies on frameworks.

6. Hexagonal vs Layered Architecture

In a classic 3-tier layered model (presentation → business → data), the business layer often depends on persistence frameworks. Hexagonal architecture makes dependencies explicit through ports and keeps the core isolated.

  • Layered: organized by technical role (UI, services, DAO).
  • Hexagonal: organized by domain and interaction direction (inbound vs outbound).

Both can coexist: you may structure packages by layer inside each adapter module while preserving port boundaries at the application boundary.

7. Best Practices

  • Keep adapters thin: mapping, validation, and protocol translation only—no business rules.
  • Define ports in the application layer: not in infrastructure.
  • Use DTOs at the boundary: do not leak JPA entities or HTTP models into the domain.
  • Test the core without Spring: pure unit tests for use cases with fake adapters.
  • Avoid an anemic core: put business logic in the domain, not only in services.

8. When to Use Hexagonal Architecture

Good fit when:

  • the domain is non-trivial and long-lived;
  • you need high test coverage without heavy infrastructure;
  • multiple interfaces (API, events, CLI) share the same use cases;
  • infrastructure is likely to change (cloud migration, database swap).

May be overkill when:

  • the application is a simple CRUD prototype;
  • the team is small and the domain is stable and shallow;
  • delivery speed matters more than long-term modularity.

9. Conclusion

Hexagonal Architecture helps teams build systems where business logic stays independent of frameworks and infrastructure. By defining explicit ports and adapters, you gain flexibility, clearer structure, and better tests—especially in complex domains that must evolve over years.

Combined with DDD and clean dependency rules, ports and adapters become a practical way to keep complexity under control without sacrificing delivery speed once the team understands the pattern.

Resume: Hexagonal Architecture (Ports & Adapters)

Problem / need: Applications often become tightly coupled to frameworks, databases, and UI technologies, making them hard to test, evolve, and replace infrastructure components.

Proposed solution: Hexagonal architecture places the business domain at the center and connects the outside world through explicit ports and adapters, isolating application logic from technical details.

Implementation principle: It is typically implemented with a domain core surrounded by inbound adapters (REST controllers, message listeners) and outbound adapters (repositories, external API clients), often combined with dependency inversion and frameworks such as Spring Boot.



Post a Comment

0 Comments