diff --git a/.gitignore b/.gitignore index adddfd8..23fb6d7 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,8 @@ dist # TernJS port file .tern-port +# VS code internal folder +.vscode # Stores VSCode versions used for testing VSCode extensions .vscode-test diff --git a/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg b/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg new file mode 100644 index 0000000..a2c99e3 --- /dev/null +++ b/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg @@ -0,0 +1,4 @@ + + + +
Business
Component A
Business...
Core
Core
depends on
depends on
Inbound Adapters
Inbound Adapters
depends on
depends on
Outbound Adapters
Outbound Adapters
Use Cases
Use Cases
through DI
through DI
<<interface>>
Outbound Ports
<<interface>>...
Domain
Domain
Mapper
Mapper
Model
Model
Mapper
Mapper
Model
Model
executes
(Not for batch!)
executes...
Service/
Client/
Components/
...
Service/...
Persistence
/ Service
/ Components
Persistence...
Services
Services
implements
implements
calls
calls
calls
calls
<<interface>>
Inbound Ports
<<interface>>...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index db0bab4..91b9303 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -2,6 +2,7 @@ ** xref:getting_started/create_rest_service.adoc[] * Architecture ** xref:architecture/layered_architecture.adoc[] +** xref:architecture/hexagonale_architecture.adoc[] * Integration ** xref:integration/rest_service.adoc[] diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc new file mode 100644 index 0000000..6485090 --- /dev/null +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -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." <> +==== + +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.