-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from baumeister25/feature/hex_arch
Adding hexagonal architecture as package structure
- Loading branch information
Showing
4 changed files
with
207 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
200 changes: 200 additions & 0 deletions
200
modules/ROOT/pages/architecture/hexagonale_architecture.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
:imagesdir: ../images | ||
= Hexagonal Architecture | ||
|
||
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. | ||
The core contains the business logic and communicates with the external world via well-defined ports (interfaces). | ||
Adapters implement these ports and handle the translation between the core's domain model and the specific technologies or protocols used by external systems. | ||
|
||
[[img-t-hexagonal-architecture]] | ||
.Hexagonal Architecture Reference | ||
image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal architecture blueprint",scaledwidth="80%",align="center"] | ||
// ---- | ||
//Created the directory tree based on this list using https://tree.nathanfriend.io/ | ||
// 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: | ||
|
||
// - application | ||
// - core | ||
// - domain | ||
// - Customer.java | ||
// - CustomerFactory.java | ||
// - Reservation.java | ||
// - Table.java | ||
// - ports | ||
// - in | ||
// - AddReservationPort.java | ||
// - CancelReservationPort.java | ||
// - AlterReservationPort.java | ||
// - AddTablePort.java | ||
// - RemoveTablePort.java | ||
// - out | ||
// - StoreReservationPort.java | ||
// - usecase | ||
// - AddReservationUc.java | ||
// - ManageReservationUc.java | ||
// - AddTableUc.java | ||
// - [service] | ||
// - [FindFreeTableService.java] | ||
// - adapter | ||
// - in | ||
// - rest | ||
// - RestController.java | ||
// - model | ||
// - ReservationDto.java | ||
// - mapper | ||
// - ReservationMapper.java | ||
// - out | ||
// - jpa | ||
// - JpaAdapter.java | ||
// - model | ||
// - ReservationEntity.java | ||
// - TableEntity.java | ||
// - mapper | ||
// - ReservationJpaMapper.java | ||
// - TableJpaMapper.java | ||
// ---- | ||
|
||
[source,plaintext] | ||
---- | ||
. | ||
└── application/ | ||
├── core/ | ||
│ ├── domain/ | ||
│ │ ├── Customer.java | ||
│ │ ├── CustomerFactory.java | ||
│ │ ├── Reservation.java | ||
│ │ └── Table.java | ||
│ ├── ports/ | ||
│ │ ├── in/ | ||
│ │ │ ├── AddReservationPort.java | ||
│ │ │ ├── CancelReservationPort.java | ||
│ │ │ ├── AlterReservationPort.java | ||
│ │ │ ├── AddTablePort.java | ||
│ │ │ └── RemoveTablePort.java | ||
│ │ └── out/ | ||
│ │ └── StoreReservationPort.java | ||
│ ├── usecase/ | ||
│ │ ├── AddReservationUc.java | ||
│ │ ├── ManageReservationUc.java | ||
│ │ └── AddTableUc.java | ||
│ └── [service]/ | ||
│ └── [FindFreeTableService.java] | ||
└── adapter/ | ||
├── in/ | ||
│ └── rest/ | ||
│ ├── RestController.java | ||
│ ├── model/ | ||
│ │ └── ReservationDto.java | ||
│ └── mapper/ | ||
│ └── ReservationMapper.java | ||
└── out/ | ||
└── jpa/ | ||
├── JpaAdapter.java | ||
├── model/ | ||
│ ├── ReservationEntity.java | ||
│ └── TableEntity.java | ||
└── mapper/ | ||
├── ReservationJpaMapper.java | ||
└── TableJpaMapper.java | ||
---- | ||
[cols="20,~", options="header"] | ||
|=== | ||
| Package | Description | ||
|
||
| core | ||
| 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. | ||
|
||
| core.domain | ||
| The domain package contains the entities and value objects of the business domain of the application. | ||
Related Factories or Builders are located here as well. | ||
It's proposed to make entities anemic. See <<_anemic_vs_rich_domain_models>> | ||
|
||
| core.usecase | ||
| Use Cases are the main entrypoint of the applications core. | ||
They validate the given input and orchestrate the domain entities, services and ports to implement a Business Use Case. | ||
Usually a use case implementation should only include a small dedicated use case. | ||
Depending of the size and adjacency of the use cases a grouping might make sense (e.g. ManageTableUc) | ||
|
||
| core.port | ||
| Ports are interfaces, that are used by the core and should be implemented by an according adapter. | ||
Ports should not be technology specific. | ||
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. | ||
It needs to be distinguished between incoming ports and outgoing ports. | ||
|
||
| core.port.in | ||
| Incoming ports are the entry of the application. | ||
They provide interfaces that are called from incoming adapters and hide the actual implementation. | ||
A proposal of structuring incoming ports is naming them like single use cases (e.g. CancelReservationPort). | ||
Each port should only provide a single method. | ||
.Design Decision | ||
[%collapsible] | ||
==== | ||
Incoming Ports are not as relevant for the hexagonal architecture as the outgoing ports. | ||
Outgoing ports are used for the dependency inversion pattern. | ||
For incoming ports could also call the use cases directly. | ||
Therefore, an pragmatic alternative would be leaving out the incoming ports. | ||
|
||
It was decided to include the incoming ports nonetheless. They should implement single use cases that are offered. | ||
Each interface should clearly mark the use case that contains only one method. | ||
Use cases from the interface might be grouped logically in the use case implementation class. | ||
==== | ||
|
||
| core.port.out | ||
| Outgoing ports are an abstraction of everything in the surrounding context that is actively triggered by the core or used as data sink. | ||
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. | ||
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. | ||
This way a technology can be easily replaced. | ||
For example storing the reservation could be be realized in a first prototype by writing the objects to a file. | ||
Later it could be replaced with a database. | ||
The core logic would be untouched by that. | ||
|
||
| [optional] core.service | ||
| Services can be considered as business helper classes. | ||
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. | ||
Services are optional as they can be used, when there's a real need. | ||
Usually a use case should contain the business logic. | ||
|
||
| adapter | ||
a| Adapters connect the application core to the surrounding context. They have the following tasks: | ||
|
||
* Implement a specific protocol to connect to the context. E.g REST, JDBC, MQTT, ... | ||
* Maintain a data model that is necessary to communicate with the context | ||
* Translate the domain model from the core to that model or vice versa | ||
* Handle protocol specific errors | ||
* Log the interaction with the surrounding context | ||
| adapter.in | ||
| Incoming adapters specify connection points for everything that can trigger the business logic. | ||
That might be interfaces (HTML, RPC, etc), Message Consumers or schedulers for batch processing. | ||
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.web`). | ||
|
||
| adapter.out | ||
| Outgoing adapters define outgoing connections where the application actively interacts with context outside. | ||
That can be database connections, file operations, API calls, message producing and many more. | ||
Inside the adapters further packages are differentiating the category of the adapter (e.g. `.repository`). | ||
|=== | ||
|
||
|
||
== Anemic vs Rich domain models | ||
==== | ||
"In a rich domain model, as much of the domain logic as possible is implemented within the entities at the core of the application. | ||
The entities provide methods to change state and only allow changes that are valid according to the business rules. [...] | ||
In an “anemic” domain model, the entities themselves are very thin. | ||
They usually only provide fields to hold." <<Hombergs21>> | ||
==== | ||
|
||
Considering java as an object oriented language it feels natural to implement business logic inside the entities themselves. | ||
In large scale application we propose to not use rich domain models. | ||
There are two reasons for this: | ||
|
||
. the domain objects are returned to the adapters. | ||
If they include business logic this is revealed and available outside of the core, which should not be the case. | ||
The answer to this problem could be an additional mapping, but this leads to a lot of unpractical mappings. | ||
. adding the business logic to the domain entities spreads it across use cases, entities and services. | ||
This makes the application more difficult to understand and harder to locate the place for new features or changes. | ||
|
||
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. | ||
|
||
|
||
[bibliography] | ||
== Bibliography | ||
* [[[Hombergs21]]] Tom Hombergs. _Get Your Hands Dirty on Clean Architecture._ 2021. |