Unravel โบ Java concepts โบ Design patterns and architecture
Layered and hexagonal architecture
Enterprise code is usually split into layers (controller, service, repository) so that each class has one job.
Learn these first
At a glance
A restaurant: the dining room (controller) takes orders, the kitchen (service) decides how to cook, and the pantry (repository) holds ingredients.
- 1Layered: the controller handles HTTP and validation, the service holds business rules and transactions, the repository talks to the databaseCalls go downward only; a repository never calls a controller.
- 2Entities and DTOs cross layers differently: the web layer uses request/response DTOs, the service uses domain objects, the repository uses entities or rows
- 3Hexagonal (ports and adapters): the core defines interfaces such as PaymentPort and OrderRepositoryPortAdapters (StripePaymentAdapter, JpaOrderRepository) implement them. The core has no import of Spring or JPA types.
- 4Benefits: the core is tested with plain unit tests and fakes, and a technology (database, message broker, payment provider) can be replaced by writing one new adapter
- Do not add layers without purpose
- Transactions belong on the service (the unit of business work), not on controllers or repositories
- Dependency inversion makes the arrow point inward: source code dependencies go from adapters to the core, while at runtime control flows both ways
public interface PaymentPort { PaymentResult charge(OrderId id, Money amount);}@Servicepublic class CheckoutUseCase { private final PaymentPort payments; private final OrderRepositoryPort orders; public CheckoutUseCase(PaymentPort payments, OrderRepositoryPort orders) { this.payments = payments; this.orders = orders; } public void checkout(OrderId id) { Order order = orders.load(id); payments.charge(id, order.total()); orders.save(order.markPaid()); }}- line 1 uses Port, the core of Layered and hexagonal architecture
- line 5 uses Service, the core of Layered and hexagonal architecture
- line 6 uses UseCase, the core of Layered and hexagonal architecture
- line 7 uses Port, the core of Layered and hexagonal architecture
- line 8 uses Repository, the core of Layered and hexagonal architecture
- line 10 uses UseCase, the core of Layered and hexagonal architecture
Read the full explanation (about 2 min)
Enterprise code is usually split into layers (controller, service, repository) so that each class has one job. Hexagonal architecture goes further: the business core depends on interfaces (ports) and the database or web framework plugs in from outside (adapters).
A mental model
A restaurant: the dining room (controller) takes orders, the kitchen (service) decides how to cook, and the pantry (repository) holds ingredients. In the hexagonal version the kitchen only knows "I need flour" (a port) and does not care which supplier delivers it.
How it works
Layered: the controller handles HTTP and validation, the service holds business rules and transactions, the repository talks to the database. Calls go downward only; a repository never calls a controller.
Entities and DTOs cross layers differently: the web layer uses request/response DTOs, the service uses domain objects, the repository uses entities or rows.
Hexagonal (ports and adapters): the core defines interfaces such as PaymentPort and OrderRepositoryPort. Adapters (StripePaymentAdapter, JpaOrderRepository) implement them. The core has no import of Spring or JPA types.
Benefits: the core is tested with plain unit tests and fakes, and a technology (database, message broker, payment provider) can be replaced by writing one new adapter.
Going deeper
Do not add layers without purpose. A service that only forwards calls to a repository adds noise; start simple and extract when behaviour appears.
Transactions belong on the service (the unit of business work), not on controllers or repositories. A controller that opens a transaction hides slow I/O inside it.
Enforce boundaries with tests (ArchUnit rules such as "domain must not depend on web") or modules (Spring Modulith), otherwise they erode with every shortcut.
Under the hood
Dependency inversion makes the arrow point inward: source code dependencies go from adapters to the core, while at runtime control flows both ways. The container wires the implementation of each port into the core at startup, which is exactly what dependency injection is for.
Common mistakes
- Business rules in controllers or repositories.
- Core classes importing framework types.
- Anaemic services that only delegate.
- Letting layers call upwards or skip layers randomly.
How Unravel recognises it
When you paste code, Unravel looks for these signs of layered and hexagonal architecture. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@Port@Adapter@UseCase@DomainService@AggregateRoot@ValueObject@Modulith@ApplicationModule@RestController@Service@Repository@Entity - Types:
ApplicationModulesโฆPortโฆUseCaseโฆAdapterโฆGateway
Part of a learning path
- Structuring an enterprise service ยท previous: Spring beans and dependency injection ยท next: DTOs and object mapping (MapStruct, ModelMapper)
Related concepts
Frequently asked questions
What is Layered and hexagonal architecture?
Enterprise code is usually split into layers (controller, service, repository) so that each class has one job. Hexagonal architecture goes further: the business core depends on interfaces (ports) and the database or web framework plugs in from outside (adapters).
What are the most common mistakes with Layered and hexagonal architecture?
Business rules in controllers or repositories. Core classes importing framework types. Anaemic services that only delegate. Letting layers call upwards or skip layers randomly.
How can I see Layered and hexagonal architecture in real code?
Paste your own Java into Unravel and it will point out where layered and hexagonal architecture appears, explain how it works at the detail level you choose, and draw the flow of the code around it.