Hexagonal Architecture Ports and Adapters Guide
Hexagonal architecture ports and adapters pattern isolates your business logic from external concerns like databases, APIs, and UI frameworks. Therefore, your domain code remains testable and independent of infrastructure decisions. This guide walks through practical implementation with real-world examples.
Understanding the Core Concept
Traditional layered architectures create tight coupling between business logic and infrastructure. Moreover, database queries leak into service classes, making unit testing difficult. As a result, changing your persistence layer requires rewriting business logic.
In contrast, hexagonal architecture defines clear boundaries through ports and adapters. Consequently, the domain core depends on nothing external and communicates only through well-defined interfaces.
System design showing hexagonal boundaries between domain and infrastructure
Implementing Hexagonal Architecture Ports in Java
Ports are interfaces that define how the outside world interacts with your domain. Specifically, driving ports represent use cases that external actors trigger, while driven ports define what the domain needs from infrastructure:
// Driving port (use case)
public interface CreateOrderUseCase {
OrderId execute(CreateOrderCommand command);
}
// Driven port (infrastructure need)
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(OrderId id);
}
// Domain service implementing the use case
public class OrderService implements CreateOrderUseCase {
private final OrderRepository repository;
private final PaymentGateway paymentGateway;
public OrderId execute(CreateOrderCommand cmd) {
Order order = Order.create(cmd.customerId(), cmd.items());
paymentGateway.charge(order.total());
return repository.save(order).getId();
}
}
Adapters implement these ports for specific technologies. Therefore, a PostgreSQL adapter implements OrderRepository, while a Stripe adapter implements PaymentGateway.
Driving and Driven Adapters
Driving adapters call into the domain through driving ports. For example, a REST controller is a driving adapter that translates HTTP requests into use case calls. Furthermore, CLI commands, GraphQL resolvers, and message consumers all serve as driving adapters.
Driven adapters are called by the domain through driven ports. Specifically, database repositories, email services, and external API clients are driven adapters. However, the domain never depends on adapter implementations directly.
Clear separation between driving and driven adapters around the domain core
Testing Benefits of Port-Based Design
The greatest advantage of this architecture is testability. Additionally, you can test the entire domain logic with simple in-memory adapter implementations. As a result, tests run in milliseconds without databases, networks, or external services.
Moreover, integration tests swap only the specific adapters under test. Consequently, you achieve high coverage with fast, reliable test suites.
When to Choose This Pattern
Hexagonal architecture shines in complex domains with multiple integration points. Meanwhile, simple CRUD applications may not benefit from the additional structure. Therefore, evaluate your domain complexity before committing to this pattern.
Evaluating architectural patterns for domain-driven applications
Related Reading:
Further Resources:
In conclusion, hexagonal architecture ports and adapters create maintainable systems by inverting dependencies and isolating domain logic. Therefore, adopt this pattern when your application demands testability and long-term flexibility.