Skip to content

Commit 6ddfc90

Browse files
authored
Merge pull request #39 from baumeister25/feature/hex_arch
Adding hexagonal architecture as package structure
2 parents cec4da9 + b6519c6 commit 6ddfc90

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ dist
119119
# TernJS port file
120120
.tern-port
121121

122+
# VS code internal folder
123+
.vscode
122124
# Stores VSCode versions used for testing VSCode extensions
123125
.vscode-test
124126

modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg

+4
Loading

modules/ROOT/nav.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
** xref:getting_started/create_rest_service.adoc[]
33
* Architecture
44
** xref:architecture/layered_architecture.adoc[]
5+
** xref:architecture/hexagonale_architecture.adoc[]
56
67
* Integration
78
** xref:integration/rest_service.adoc[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
:imagesdir: ../images
2+
= Hexagonal Architecture
3+
4+
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.
7+
8+
[[img-t-hexagonal-architecture]]
9+
.Hexagonal Architecture Reference
10+
image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal architecture blueprint",scaledwidth="80%",align="center"]
11+
// ----
12+
//Created the directory tree based on this list using https://tree.nathanfriend.io/
13+
// 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+
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+
// - rest
40+
// - RestController.java
41+
// - model
42+
// - ReservationDto.java
43+
// - mapper
44+
// - ReservationMapper.java
45+
// - out
46+
// - jpa
47+
// - JpaAdapter.java
48+
// - model
49+
// - ReservationEntity.java
50+
// - TableEntity.java
51+
// - mapper
52+
// - ReservationJpaMapper.java
53+
// - TableJpaMapper.java
54+
// ----
55+
56+
[source,plaintext]
57+
----
58+
.
59+
└── application/
60+
├── core/
61+
│ ├── domain/
62+
│ │ ├── Customer.java
63+
│ │ ├── CustomerFactory.java
64+
│ │ ├── Reservation.java
65+
│ │ └── Table.java
66+
│ ├── ports/
67+
│ │ ├── in/
68+
│ │ │ ├── AddReservationPort.java
69+
│ │ │ ├── CancelReservationPort.java
70+
│ │ │ ├── AlterReservationPort.java
71+
│ │ │ ├── AddTablePort.java
72+
│ │ │ └── RemoveTablePort.java
73+
│ │ └── out/
74+
│ │ └── StoreReservationPort.java
75+
│ ├── usecase/
76+
│ │ ├── AddReservationUc.java
77+
│ │ ├── ManageReservationUc.java
78+
│ │ └── AddTableUc.java
79+
│ └── [service]/
80+
│ └── [FindFreeTableService.java]
81+
└── adapter/
82+
├── in/
83+
│ └── rest/
84+
│ ├── RestController.java
85+
│ ├── model/
86+
│ │ └── ReservationDto.java
87+
│ └── mapper/
88+
│ └── ReservationMapper.java
89+
└── out/
90+
└── jpa/
91+
├── JpaAdapter.java
92+
├── model/
93+
│ ├── ReservationEntity.java
94+
│ └── TableEntity.java
95+
└── mapper/
96+
├── ReservationJpaMapper.java
97+
└── TableJpaMapper.java
98+
----
99+
[cols="20,~", options="header"]
100+
|===
101+
| Package | Description
102+
103+
| core
104+
| 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+
106+
| 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.
109+
It's proposed to make entities anemic. See <<_anemic_vs_rich_domain_models>>
110+
111+
| 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.
115+
Depending of the size and adjacency of the use cases a grouping might make sense (e.g. ManageTableUc)
116+
117+
| 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.
121+
It needs to be distinguished between incoming ports and outgoing ports.
122+
123+
| core.port.in
124+
| 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).
127+
Each port should only provide a single method.
128+
.Design Decision
129+
[%collapsible]
130+
====
131+
Incoming Ports are not as relevant for the hexagonal architecture as the outgoing ports.
132+
Outgoing ports are used for the dependency inversion pattern.
133+
For incoming ports could also call the use cases directly.
134+
Therefore, an pragmatic alternative would be leaving out the incoming ports.
135+
136+
It was decided to include the incoming ports nonetheless. They should implement single use cases that are offered.
137+
Each interface should clearly mark the use case that contains only one method.
138+
Use cases from the interface might be grouped logically in the use case implementation class.
139+
====
140+
141+
| core.port.out
142+
| Outgoing ports are an abstraction of everything in the surrounding context that is actively triggered by the core or used as data sink.
143+
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.
144+
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.
145+
This way a technology can be easily replaced.
146+
For example storing the reservation could be be realized in a first prototype by writing the objects to a file.
147+
Later it could be replaced with a database.
148+
The core logic would be untouched by that.
149+
150+
| [optional] core.service
151+
| Services can be considered as business helper classes.
152+
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.
153+
Services are optional as they can be used, when there's a real need.
154+
Usually a use case should contain the business logic.
155+
156+
| adapter
157+
a| Adapters connect the application core to the surrounding context. They have the following tasks:
158+
159+
* Implement a specific protocol to connect to the context. E.g REST, JDBC, MQTT, ...
160+
* Maintain a data model that is necessary to communicate with the context
161+
* Translate the domain model from the core to that model or vice versa
162+
* Handle protocol specific errors
163+
* Log the interaction with the surrounding context
164+
165+
| adapter.in
166+
| Incoming adapters specify connection points for everything that can trigger the business logic.
167+
That might be interfaces (HTML, RPC, etc), Message Consumers or schedulers for batch processing.
168+
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.web`).
169+
170+
| adapter.out
171+
| Outgoing adapters define outgoing connections where the application actively interacts with context outside.
172+
That can be database connections, file operations, API calls, message producing and many more.
173+
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.repository`).
174+
|===
175+
176+
177+
== Anemic vs Rich domain models
178+
====
179+
"In a rich domain model, as much of the domain logic as possible is implemented within the entities at the core of the application.
180+
The entities provide methods to change state and only allow changes that are valid according to the business rules. [...]
181+
In an “anemic” domain model, the entities themselves are very thin.
182+
They usually only provide fields to hold." <<Hombergs21>>
183+
====
184+
185+
Considering java as an object oriented language it feels natural to implement business logic inside the entities themselves.
186+
In large scale application we propose to not use rich domain models.
187+
There are two reasons for this:
188+
189+
. the domain objects are returned to the adapters.
190+
If they include business logic this is revealed and available outside of the core, which should not be the case.
191+
The answer to this problem could be an additional mapping, but this leads to a lot of unpractical mappings.
192+
. adding the business logic to the domain entities spreads it across use cases, entities and services.
193+
This makes the application more difficult to understand and harder to locate the place for new features or changes.
194+
195+
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.
196+
197+
198+
[bibliography]
199+
== Bibliography
200+
* [[[Hombergs21]]] Tom Hombergs. _Get Your Hands Dirty on Clean Architecture._ 2021.

0 commit comments

Comments
 (0)