Hexagonal Architecture in Spring Boot
Hexagonal Architecture, also known as Ports and Adapters, is a design pattern aimed at isolating the central logic of an application from external technical concerns like databases, user interfaces, or third-party services.
In the Spring Boot ecosystem, it’s common to find projects where business logic is tightly coupled with JPA annotations or web controller dependencies. This article explores how to break that coupling.
Why Hexagonal?
Imagine your application as a “hexagon.” At its center lies the Domain, containing pure business rules. Nothing from the outside should contaminate this core. Communication with the external world happens through:
- Ports: Interfaces that define what the domain wants to do, but not how.
- Adapters: Concrete implementations that translate external calls into the domain (Primary Adapters) or from the domain to the outside world (Secondary Adapters).
Project Structure
A typical structure following this pattern would look like this:
com.example.project├── domain│ ├── model (Pure POJO classes)│ ├── service (Business logic)│ └── ports (Input and output interfaces)├── infrastructure│ ├── adapters│ │ ├── input (REST Controllers, CLI)│ │ └── output (JPA Repositories, HTTP Clients)│ └── config (Spring Configuration)1. The Domain Core
This is where your application’s “truth” resides. We avoid using annotations like @Entity or @Table here to keep it independent from JPA.
public class Order { private UUID id; private List<Product> products; private OrderStatus status;
public void confirm() { if (products.isEmpty()) { throw new BusinessException("Cannot confirm an empty order"); } this.status = OrderStatus.CONFIRMED; }}2. The Ports
We define interfaces for our requirements. For instance, an output port to save orders:
public interface OrderRepositoryPort { Order save(Order order); Optional<Order> findById(UUID id);}3. The Adapters
A secondary adapter would implement the output port using JPA:
@Componentpublic class JpaOrderAdapter implements OrderRepositoryPort { private final SpringDataOrderRepository repository;
@Override public Order save(Order order) { OrderEntity entity = OrderMapper.toEntity(order); return OrderMapper.toDomain(repository.save(entity)); }}Benefits of this Approach
- Testability: You can test business logic without spinning up a Spring context or a real database.
- Technological Independence: If you decide to switch from MySQL to MongoDB, you only change the infrastructure adapter; the domain remains untouched.
- Long-term Maintainability: The code is easier to read, and responsibilities are clearly delimited.
Conclusion
Implementing Hexagonal Architecture in Spring Boot requires more initial effort due to the creation of mappers and multiple layers. However, for applications meant to grow and evolve over years, it’s one of the best investments a developer can make.