You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hexagonal architecture, also known as Ports and Adapters, is a software design pattern that promotes separation of concerns by organizing an application into a central core surrounded by external adapters.
5
-
The core contains the business logic and communicates with the external world via well-defined ports (interfaces).
6
-
Adapters implement these ports and handle the translation between the core's domain model and the specific technologies or protocols used by external systems.
4
+
In many software systems, core business logic is entangled with technical concerns like persistence, messaging, and protocol handling. This coupling makes systems fragile, difficult to adapt, and hard to test.
As illustrated in Figure 1 (_Infrastructural Entanglement_), application logic is directly tied to external systems — databases, user interfaces, messaging infrastructure — via concrete implementations throughout the application. Domain models are aware of their environment and dependencies are pervasive: HTTP controllers call service methods that operate on ORM-managed entities; repositories return framework-specific types; and cross-cutting concerns such as error handling, logging, and authentication are scattered throughout the codebase.
10
+
11
+
This tight coupling leads to several recurring challenges:
12
+
13
+
- *Hard to adapt:* Infrastructure changes (e.g., migrating from a relational database to NoSQL, or shifting from REST to asynchronous messaging) require invasive changes to business logic, even if functional requirements stay the same.
14
+
- *Difficult to test:* Business logic is hard to isolate for unit testing due to deep technical dependencies. Tests either require real external systems or rely on brittle mocking setups.
15
+
- *Blurred concerns:* Business logic is mixed with infrastructure logic — transaction management, logging, exception mapping — making it harder to understand, modify, or reuse.
16
+
17
+
While traditional layered architectures (e.g., presentation → service → persistence) attempt to organize these concerns, they often still expose the domain layer to infrastructure details. In contrast, hexagonal architecture addresses these challenges through more structural response that utilizes the dependency inversion principle.
18
+
19
+
== Hexagonal Blueprint
20
+
21
+
Hexagonal Architecture — also known as the _Ports and Adapters_ pattern — is an architectural style that centers the domain model, surrounded by abstract interfaces to the outside world. These interfaces, called *ports*, act like sockets through which external components interact with the core. Concrete implementations — known as *adapters* — connect to these ports and handle the specifics of communication, persistence, or protocol integration.
22
+
23
+
This design follows the *Dependency Inversion Principle*: the domain depends only on abstractions, not specific technologies. It neither knows nor cares what framework, database, or transport mechanism is in use — only that the contractual behavior of the port is respected.
24
+
25
+
Adapter wiring is delegated to the runtime environment, often via a dependency injection framework such as Spring or Quarkus. This allows the core to remain agnostic of instantiation logic and independent of its execution environment.
The resulting structure is shown in Figure 2 (_Hexagonal Architecture in Design_): the business logic resides at the core, surrounded by inbound and outbound ports. Adapters implement these ports and handle concerns such as user input, persistence, messaging, or observability — without leaking into the domain layer.
31
+
32
+
== Practical Example
33
+
34
+
To make this structure concrete, consider a system where two application use cases — reservation and batch processing — form the core of the business logic.
This setup is shown in Figure 3 (_Hexagonal Architecture in Practice_). Both use cases are implemented inside the core and exposed through dedicated interfaces: `ReservationPort` and `BatchPort`. These interfaces form the system’s inbound ports — stable, technology-agnostic entry points into the domain.
40
+
41
+
External inputs are handled by inbound adapters. A `RestController` invokes `ReservationPort` to process HTTP requests. The `GrpcAdapter` implements `BatchPort`, allowing background tasks or other systems to initiate batch processing via gRPC. These adapters translate protocols and delegate to the core without introducing infrastructure-specific concerns into the domain.
42
+
43
+
The domain, in turn, depends on two outbound ports: `PersistencePort` and `PaymentPort`. These define how the domain interacts with data stores and external services. Their implementations — `JpaAdapter` and `SoapAdapter`, respectively — encapsulate technical details such as database access or remote service communication.
44
+
45
+
Because the core depends only on ports, it stays isolated from the specifics of HTTP, gRPC, JPA, or SOAP. Replacing an adapter — for example, swapping SOAP for a REST-based payment gateway — does not require any changes to the domain layer. This separation keeps infrastructure decisions localized and reversible while ensuring that the core remains stable and testable.
46
+
47
+
== Dependency Direction and Data Ownership
48
+
49
+
Hexagonal Architecture enforces a strict rule: dependencies point inward. Adapters may depend on the domain, but the domain must not depend on adapter code — not even indirectly through return types. This constraint has important implications for how data flows across layers.
This is illustrated in Figure 4 (_Zooming into the Persistence Port_). For example, when the domain calls `PersistencePort` and expects a return value, the adapter must ensure that no infrastructure-specific types leak into the core. Returning a `ReservationEntity` — a JPA class defined in the adapter — would violate this rule by making the domain aware of persistence details.
55
+
56
+
There are two primary strategies to preserve the direction of dependency:
57
+
58
+
- *Explicit Mapping Inside the Adapter:*
59
+
The adapter maps domain objects to persistence entities for storage, and maps them back before returning results. This approach keeps the domain clean and separate, though it requires boilerplate mapping logic.
60
+
61
+
- *Domain Interfaces Implemented by the Adapter:*
62
+
Alternatively, the domain model can be defined as interfaces (e.g. `Reservation`). The adapter implements these interfaces in its entity classes. This allows the adapter to return its own types while keeping the domain dependent only on abstractions.
63
+
64
+
Each strategy has trade-offs. Mapping provides clear boundaries and flexibility but increases code volume. Interface-based design reduces duplication but may blur the separation between layers. The choice depends on the complexity of the domain and how much semantic weight the model carries.
65
+
66
+
Regardless of approach, the architectural constraint holds: data flows inward, and dependencies never reverse. The domain defines what it needs; the adapter provides it — without exposing implementation details or violating the system’s structural integrity.
67
+
68
+
=== Package Structure
69
+
The following table shows a package structure that fits the hexagonal design and focuses on the reservation use case of the application:
11
70
// ----
12
71
//Created the directory tree based on this list using https://tree.nathanfriend.io/
13
72
// As the list is easier to maintain, try to do edits in the list structure, use the tool mentioned above and paste both in here:
14
73
15
-
// - application
16
-
// - core
17
-
// - domain
18
-
// - Customer.java
19
-
// - CustomerFactory.java
20
-
// - Reservation.java
21
-
// - Table.java
22
-
// - ports
23
-
// - in
24
-
// - AddReservationPort.java
25
-
// - CancelReservationPort.java
26
-
// - AlterReservationPort.java
27
-
// - AddTablePort.java
28
-
// - RemoveTablePort.java
29
-
// - out
30
-
// - StoreReservationPort.java
31
-
// - usecase
32
-
// - AddReservationUc.java
33
-
// - ManageReservationUc.java
34
-
// - AddTableUc.java
35
-
// - [service]
36
-
// - [FindFreeTableService.java]
37
-
// - adapter
38
-
// - in
39
-
// - web
40
-
// - RestController.java
41
-
// - model
42
-
// - ReservationDto.java
43
-
// - mapper
44
-
// - ReservationMapper.java
45
-
// - out
46
-
// - repository
47
-
// - JpaAdapter.java
48
-
// - model
49
-
// - ReservationEntity.java
50
-
// - TableEntity.java
51
-
// - mapper
52
-
// - ReservationJpaMapper.java
53
-
// - TableJpaMapper.java
74
+
// application
75
+
// core
76
+
// domain
77
+
// Reservation.java
78
+
// ports
79
+
// in
80
+
// ReservationPort.java
81
+
// out
82
+
// PersistencePort.java
83
+
// usecase
84
+
// AddReservationUseCase.java
85
+
// CancelReservationUseCase.java
86
+
// PostponeReservationUseCase.java
87
+
// service
88
+
// ReservationService.java
89
+
// adapter
90
+
// in
91
+
// web
92
+
// RestController.java
93
+
// model
94
+
// ReservationDto.java
95
+
// mapper
96
+
// ReservationMapper.java
97
+
// out
98
+
// persistence
99
+
// JpaAdapter.java
100
+
// model
101
+
// ReservationEntity.java
102
+
// logic
103
+
// ReservationRepository.java
104
+
// ReservationMapper.java
54
105
// ----
55
106
56
107
[source,plaintext]
@@ -59,25 +110,18 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a
59
110
└── application/
60
111
├── core/
61
112
│ ├── domain/
62
-
│ │ ├── Customer.java
63
-
│ │ ├── CustomerFactory.java
64
-
│ │ ├── Reservation.java
65
-
│ │ └── Table.java
113
+
│ │ └── Reservation.java
66
114
│ ├── ports/
67
115
│ │ ├── in/
68
-
│ │ │ ├── AddReservationPort.java
69
-
│ │ │ ├── CancelReservationPort.java
70
-
│ │ │ ├── AlterReservationPort.java
71
-
│ │ │ ├── AddTablePort.java
72
-
│ │ │ └── RemoveTablePort.java
116
+
│ │ │ └── ReservationPort.java
73
117
│ │ └── out/
74
-
│ │ └── StoreReservationPort.java
118
+
│ │ └── PersistencePort.java
75
119
│ ├── usecase/
76
-
│ │ ├── AddReservationUc.java
77
-
│ │ ├── ManageReservationUc.java
78
-
│ │ └── AddTableUc.java
79
-
│ └── [service]/
80
-
│ └── [FindFreeTableService.java]
120
+
│ │ ├── AddReservationUseCase.java
121
+
│ │ ├── CancelReservationUseCase.java
122
+
│ │ └── PostponeReservationUseCase.java
123
+
│ └── service/
124
+
│ └── ReservationService.java
81
125
└── adapter/
82
126
├── in/
83
127
│ └── web/
@@ -87,14 +131,13 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a
87
131
│ └── mapper/
88
132
│ └── ReservationMapper.java
89
133
└── out/
90
-
└── repository/
134
+
└── persistence/
91
135
├── JpaAdapter.java
92
136
├── model/
93
-
│ ├── ReservationEntity.java
94
-
│ └── TableEntity.java
95
-
└── mapper/
96
-
├── ReservationJpaMapper.java
97
-
└── TableJpaMapper.java
137
+
│ └── ReservationEntity.java
138
+
└── logic/
139
+
├── ReservationRepository.java
140
+
└── ReservationMapper.java
98
141
----
99
142
[cols="20,~", options="header"]
100
143
|===
@@ -104,54 +147,54 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a
104
147
| The core contains the essential business logic, domain entities, and use cases. It focuses on implementing the main functionalities while remaining technology-agnostic. The core interacts with external components through well-defined interfaces called "ports," ensuring a clear separation of concerns and promoting flexibility, testability, and maintainability.
105
148
106
149
| core.domain
107
-
| The domain package contains the entities and value objects of the business domain of the application.
108
-
Related Factories or Builders are located here as well.
150
+
| The domain package contains the entities and value objects of the business domain of the application.
151
+
Related Factories or Builders are located here as well.
109
152
It's proposed to make entities anemic. See <<_anemic_vs_rich_domain_models>>
110
153
111
154
| core.usecase
112
-
| Use Cases are the main entrypoint of the applications core.
113
-
They validate the given input and orchestrate the domain entities, services and ports to implement a Business Use Case.
114
-
Usually a use case implementation should only include a small dedicated use case.
155
+
| Use Cases are the main entrypoint of the applications core.
156
+
They validate the given input and orchestrate the domain entities, services and ports to implement a Business Use Case.
157
+
Usually a use case implementation should only include a small dedicated use case.
115
158
Depending of the size and adjacency of the use cases a grouping might make sense (e.g. ManageTableUc)
116
159
117
160
| core.port
118
-
| Ports are interfaces, that are used by the core and should be implemented by an according adapter.
119
-
Ports should not be technology specific.
120
-
One big advantage of the hexagonal architecture is, that the adapters can be changed without changing the core and therefore, without touching the business logic.
161
+
| Ports are interfaces, that are used by the core and should be implemented by an according adapter.
162
+
Ports should not be technology specific.
163
+
One big advantage of the hexagonal architecture is, that the adapters can be changed without changing the core and therefore, without touching the business logic.
121
164
It needs to be distinguished between incoming ports and outgoing ports.
122
165
123
166
| core.port.in
124
167
a| Incoming ports are the entry of the application.
125
-
They provide interfaces that are called from incoming adapters and hide the actual implementation.
126
-
A proposal of structuring incoming ports is naming them like single use cases (e.g. CancelReservationPort).
168
+
They provide interfaces that are called from incoming adapters and hide the actual implementation.
169
+
A proposal of structuring incoming ports is naming them like single use cases (e.g. CancelReservationPort).
127
170
Each port should only provide a single method.
128
171
129
172
.Design Decision
130
173
[%collapsible]
131
174
====
132
175
Incoming Ports are not as relevant for the hexagonal architecture as the outgoing ports.
133
-
Outgoing ports are used for the dependency inversion pattern.
134
-
For incoming ports could also call the use cases directly.
176
+
Outgoing ports are used for the dependency inversion pattern.
177
+
For incoming ports could also call the use cases directly.
135
178
Therefore, an pragmatic alternative would be leaving out the incoming ports.
136
179
137
-
It was decided to include the incoming ports nonetheless. They should implement single use cases that are offered.
180
+
It was decided to include the incoming ports nonetheless. They should implement single use cases that are offered.
138
181
Each interface should clearly mark the use case that contains only one method.
139
182
Use cases from the interface might be grouped logically in the use case implementation class.
140
183
====
141
184
142
185
| core.port.out
143
186
| Outgoing ports are an abstraction of everything in the surrounding context that is actively triggered by the core or used as data sink.
144
187
This might include other services that are called, files that are written, databases, event streaming and everything the application is actively triggering outside of the core.
145
-
Outgoing ports should describe the business need for the communication (e.g. StoreReservationPort). How this is then realized depends on the adapter that implements it.
146
-
This way a technology can be easily replaced.
147
-
For example storing the reservation could be be realized in a first prototype by writing the objects to a file.
188
+
Outgoing ports should describe the business need for the communication (e.g. StoreReservationPort). How this is then realized depends on the adapter that implements it.
189
+
This way a technology can be easily replaced.
190
+
For example storing the reservation could be be realized in a first prototype by writing the objects to a file.
148
191
Later it could be replaced with a database.
149
192
The core logic would be untouched by that.
150
193
151
194
| [optional] core.service
152
-
| Services can be considered as business helper classes.
195
+
| Services can be considered as business helper classes.
153
196
They provide a reusable part of the applications business logic that is used by multiple use cases or that helps to structure the application in a logical way.
154
-
Services are optional as they can be used, when there's a real need.
197
+
Services are optional as they can be used, when there's a real need.
155
198
Usually a use case should contain the business logic.
156
199
157
200
| adapter
@@ -164,12 +207,12 @@ a| Adapters connect the application core to the surrounding context. They have t
164
207
* Log the interaction with the surrounding context
165
208
166
209
| adapter.in
167
-
| Incoming adapters specify connection points for everything that can trigger the business logic.
210
+
| Incoming adapters specify connection points for everything that can trigger the business logic.
168
211
That might be interfaces (HTML, RPC, etc), Message Consumers or schedulers for batch processing.
169
-
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.web`).
212
+
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.web`).
170
213
171
214
| adapter.out
172
-
| Outgoing adapters define outgoing connections where the application actively interacts with context outside.
215
+
| Outgoing adapters define outgoing connections where the application actively interacts with context outside.
173
216
That can be database connections, file operations, API calls, message producing and many more.
174
217
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.repository`).
175
218
|===
@@ -188,10 +231,10 @@ Considering java as an object oriented language it feels natural to implement bu
188
231
In large scale application we propose to not use rich domain models.
189
232
There are two reasons for this:
190
233
191
-
. the domain objects are returned to the adapters.
234
+
. the domain objects are returned to the adapters.
192
235
If they include business logic this is revealed and available outside of the core, which should not be the case.
193
236
The answer to this problem could be an additional mapping, but this leads to a lot of unpractical mappings.
194
-
. adding the business logic to the domain entities spreads it across use cases, entities and services.
237
+
. adding the business logic to the domain entities spreads it across use cases, entities and services.
195
238
This makes the application more difficult to understand and harder to locate the place for new features or changes.
196
239
197
240
Therefore, we propose to implement the domain model as anemic entities and make usage of use cases and services to implement the business logic and interact with the domain models.
0 commit comments