← Back to blog
Architecture

Towards hexagonal architecture: lessons learned

After several projects implemented with hexagonal architecture (Ports & Adapters), here's an honest review of what it truly delivers and the pitfalls to avoid.

Hexagonal architecture β€” also known as Ports & Adapters β€” was formalised by Alistair Cockburn in 2005. The goal is to strictly separate the business core from any technical concern: database, web framework, message broker, external API. These infrastructure details must never dictate the shape of the domain.

The application is structured in three concentric layers:

  • The domain, at the centre: entities, use cases, business rules β€” no dependencies toward the outside.
  • Ports: interfaces that define inbound contracts (inbound ports, triggered by primary adapters) and outbound contracts (outbound ports, implemented by secondary adapters).
  • Adapters: the concrete implementations plugged into those ports β€” REST controllers, JPA repositories, API clients, message brokers, or automated tests.
Hexagonal architecture diagram β€” domain at the centre, inbound and outbound ports, primary and secondary adapters

The fundamental rule: dependencies only point inward. The domain never knows about the adapters. Switching databases, migrating to a different broker or replacing an API client has no impact on the business code β€” only the adapter changes.

This approach is conceptually close to Martin's Clean Architecture, with different terminology. The application is at the center, surrounded by ports (interfaces defining how the application communicates with the outside) and adapters (concrete implementations: REST controllers, JPA repositories, third-party API clients). Dependencies always point toward the center.

In practice on a medium-sized Spring Boot project, hexagonal architecture transformed our testing ability. Business use cases are tested unit-style without starting the Spring context, without a database, without an HTTP server. We inject mocks through port interfaces and test pure logic. Feedback loop is immediate. Integration tests focus on adapters β€” verifying the JPA repository does what the port expects, that the REST controller serializes correctly.

Identified pitfalls: over-splitting into ports/adapters for simple operations that could have remained in a standard repository. The DTO ↔ Domain ↔ Persistence mapping multiplies objects and conversion code β€” acceptable for a rich domain, painful for a CRUD. Our recommendation: adopt hexagonal architecture for bounded contexts with heavy business logic. For a system's CRUD parts, a classic layered architecture (Controller β†’ Service β†’ Repository) is often sufficient and more pragmatic.

β†’ See also: Clean Architecture Β· Domain-Driven Design Β· SOLID principles

Have a project in mind?

Let's talk about your challenges and see how Gotan can help.

Contact us