From 1da164336527a482a05932c8ed4ac36a883b2a52 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Fri, 24 Mar 2023 08:27:34 +0100 Subject: [PATCH 01/10] Add tree structure --- .../architecture/hexagonale_architecture.adoc | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 modules/ROOT/pages/architecture/hexagonale_architecture.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..54053bb --- /dev/null +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -0,0 +1,48 @@ +// https://tree.nathanfriend.io/ +// Questions: If domain cannot access anything, how does the factory acess service to create a entity (for example using the postalcode service to get the user) +// +---- +- application + - config + - Configuration.java + - core + - domain + - User.java + - AdressValueObject.java + - UserFactory.java + - usecase + - AddUserUc.java + - AddAddressUc.java + - service + - PostalFinderService.java + - ports + - StoreUserPort.java + - BroadcastUserPort.java + - PostalCodeFinderPort.java + - adapter + - in + - rest + - RestController.java + - model + - UserDto.java + - mapper + - UserRestMapper.java + - out + - jpa + - UserPersistenceAdapter.java + - model + - UserEntity.java + - AdressEntity.java + - mapper + - UserJpaMapper.java + - kafka + - UserBroadcastAdapter + - model + - UserDto.java + - mapper + - UserKafkaMapper.java + - rest + - PostalCodeAdapter.java + - model + - PostalCodeDto.java +---- \ No newline at end of file From fb9f6da3b7913d1211ac9e9c0b08c8522b694b6e Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Fri, 14 Apr 2023 16:18:11 +0200 Subject: [PATCH 02/10] Update Hexagonal architecture: Add structure, describe packages, add pictures --- ...component_architecture_overview.drawio.svg | 4 + .../architecture/hexagonale_architecture.adoc | 186 +++++++++++++----- 2 files changed, 143 insertions(+), 47 deletions(-) create mode 100644 modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg 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..ee2b49c --- /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>>
Ports
<<interface>>...
Domain
Domain
Mapper
Mapper
Model
Model
Mapper
Mapper
Model
Model
executes
(Not for batch!)
executes...
Client
Client
Persistence
/ Service
/ Components
Persistence...
Services
Services
*Adapter
*Adapter
implements
implements
*Controller/
*Listener/
*BatchExecutor
*Controller/...
calls
calls
calls
calls
Text is not SVG - cannot display
\ No newline at end of file diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 54053bb..956cdfe 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -1,48 +1,140 @@ -// https://tree.nathanfriend.io/ -// Questions: If domain cannot access anything, how does the factory acess service to create a entity (for example using the postalcode service to get the user) -// +:imagesdir: ../images +// Open Questions/Discussion: +// - If domain cannot access anything, how does the factory acess service to create a entity (for example using the postalcode service to get the user) +// - Is everything considered in the structure or are there things we cannot handle currently? +// - What about shared technical elements? (AOP for logging accross multiple rest adapters) +// - How to organize mutliple modules in a modular monolith and what about shared stuff there? +// - Should we really name the adapters after the protocol or more speaking (e.g. reservation for a inbound adapter for reservation) +// - Is structurering core and adapters in packages enough or should we use gradle modules or java modules? + +// Todos: +// - Create ADR (Architecture Design records) +// - Mention that the structure can be made more lean at any time. Consider three case: +// - Complex application with great logic (Probably JPA and entities different, also a great core layer) +// - Application containing logic, but entities and JPA do not differ (potentially a case where the entities in Core can contain JPA specific annotations t avoid mapping) +// - Pure CRUD application (Leave out core) +// - Add an ArchUnit example to make call options explicit for devs (and reusable) += 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 +// - config +// - Configuration.java +// - core +// - domain +// - Customer.java +// - CustomerFactory.java +// - Reservation.java +// - Table.java +// - ports +// - StoreReservationPort.java +// - service +// - FindTableService.java --> diese logik Würde ich eher in einem RestaurantEntity abbilden +// - usecase +// - AddReservationUc.java +// - AddTableUc.java +// - adapter +// - in +// - rest --> Discussion point (why not more business drivem (what does it do)) +// - RestController.java +// - model +// - ReservationDto.java +// - mapper +// - ReservationMapper.java +// - out +// - jpa +// - .java +// - model +// - ReservationEntity.java +// - TableEntity.java +// - mapper +// - ReservationJpaMapper.java +// - TableJpaMapper.java +// ---- +[source,plaintext] ---- -- application - - config - - Configuration.java - - core - - domain - - User.java - - AdressValueObject.java - - UserFactory.java - - usecase - - AddUserUc.java - - AddAddressUc.java - - service - - PostalFinderService.java - - ports - - StoreUserPort.java - - BroadcastUserPort.java - - PostalCodeFinderPort.java - - adapter - - in - - rest - - RestController.java - - model - - UserDto.java - - mapper - - UserRestMapper.java - - out - - jpa - - UserPersistenceAdapter.java - - model - - UserEntity.java - - AdressEntity.java - - mapper - - UserJpaMapper.java - - kafka - - UserBroadcastAdapter - - model - - UserDto.java - - mapper - - UserKafkaMapper.java - - rest - - PostalCodeAdapter.java - - model - - PostalCodeDto.java ----- \ No newline at end of file +application/ +├── config/ +│ └── Configuration.java +├── core/ +│ ├── domain/ +│ │ ├── Customer.java +│ │ ├── CustomerFactory.java +│ │ ├── Reservation.java +│ │ └── Table.java +│ ├── ports/ +│ │ └── StoreReservationPort.java +│ ├── service/ +│ │ └── FindTableService.java +│ └── usecase/ +│ ├── AddReservationUc.java +│ └── AddTableUc.java +└── adapter/ + ├── in/ + │ └── rest + │ ├── RestController.java + │ ├── model/ + │ │ └── ReservationDto.java + │ └── mapper/ + │ └── ReservationMapper.java + └── out/ + └── jpa/ + ├── .java + ├── model/ + │ ├── ReservationEntity.java + │ └── TableEntity.java + └── mapper/ + ├── ReservationJpaMapper.java + └── TableJpaMapper.java +---- + +[cols="1,1", 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 circle contains the entities and value objects of the business domain of the application. +Related Factories or Builders are located here as well. +The entities in our domain are usually rich. + +| core.service +| Services provide a reusable part of the applications business logic that is used by multiple use cases. + +| 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. + +| 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. + +| 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 +| Inbound 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 protocols that is used (e.g. `.rest`). + +| adapter.out +| Outbound 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 protocols that is used (e.g. `.jpa`). +|=== \ No newline at end of file From a4a0fd2295ba90c56e3e4ac4e914bd04fa56a004 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Tue, 18 Apr 2023 11:05:26 +0200 Subject: [PATCH 03/10] Hexagonal Arch: Remove Factory questions as unrelated A factory should not use further service or do some "black magic" to build an object. It should get all necessary data to create the object. --- .../ROOT/pages/architecture/hexagonale_architecture.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 956cdfe..793d882 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -1,11 +1,10 @@ :imagesdir: ../images // Open Questions/Discussion: -// - If domain cannot access anything, how does the factory acess service to create a entity (for example using the postalcode service to get the user) // - Is everything considered in the structure or are there things we cannot handle currently? -// - What about shared technical elements? (AOP for logging accross multiple rest adapters) -// - How to organize mutliple modules in a modular monolith and what about shared stuff there? +// - What about shared technical elements? (AOP for logging across multiple rest adapters) +// - How to organize multiple modules in a modular monolith and what about shared stuff there? // - Should we really name the adapters after the protocol or more speaking (e.g. reservation for a inbound adapter for reservation) -// - Is structurering core and adapters in packages enough or should we use gradle modules or java modules? +// - Is structuring core and adapters in packages enough or should we use gradle modules or java modules? // Todos: // - Create ADR (Architecture Design records) @@ -61,6 +60,7 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a // - ReservationJpaMapper.java // - TableJpaMapper.java // ---- + [source,plaintext] ---- application/ From 501a8399be3ff0aae7e5c21dea97a7df096845fe Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 2 Aug 2023 08:09:31 +0200 Subject: [PATCH 04/10] fix: Add inbound ports and make services optional After dicussing with @chris-toenjes-deye we decided the following points: - Services should be marked optional. The usual business logic belongs inside the use case implementation. Only in cases of better structuring or reusable business logic, it should be moved to a service - A differentiation between inbound and outbound ports is added. Inbound ports are used to describe use cases the core provides as interfaces to the inbound adapters. Reasoning: - In the past only outbound ports where described. Use cases should be used directly. This was a pragmatic approach to reduce the effort of implementing interfaces whith the same signature as the implementation. But this differs from the idea of making the implementation transparent to the inbound adapters. Refs: #39 --- .../architecture/hexagonale_architecture.adoc | 141 ++++++++++++------ 1 file changed, 97 insertions(+), 44 deletions(-) diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 793d882..8aa9d5e 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -36,15 +36,23 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a // - Reservation.java // - Table.java // - ports -// - StoreReservationPort.java -// - service -// - FindTableService.java --> diese logik Würde ich eher in einem RestaurantEntity abbilden +// - 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 --> Discussion point (why not more business drivem (what does it do)) +// - rest // - RestController.java // - model // - ReservationDto.java @@ -52,7 +60,7 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a // - ReservationMapper.java // - out // - jpa -// - .java +// - JpaAdapter.java // - model // - ReservationEntity.java // - TableEntity.java @@ -63,39 +71,49 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a [source,plaintext] ---- -application/ -├── config/ -│ └── Configuration.java -├── core/ -│ ├── domain/ -│ │ ├── Customer.java -│ │ ├── CustomerFactory.java -│ │ ├── Reservation.java -│ │ └── Table.java -│ ├── ports/ -│ │ └── StoreReservationPort.java -│ ├── service/ -│ │ └── FindTableService.java -│ └── usecase/ -│ ├── AddReservationUc.java -│ └── AddTableUc.java -└── adapter/ - ├── in/ - │ └── rest - │ ├── RestController.java - │ ├── model/ - │ │ └── ReservationDto.java - │ └── mapper/ - │ └── ReservationMapper.java - └── out/ - └── jpa/ - ├── .java - ├── model/ - │ ├── ReservationEntity.java - │ └── TableEntity.java - └── mapper/ - ├── ReservationJpaMapper.java - └── TableJpaMapper.java +. +└── application/ + ├── config/ + │ └── Configuration.java + │ └── Configuration.java + ├── 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="1,1", options="header"] @@ -110,14 +128,49 @@ application/ Related Factories or Builders are located here as well. The entities in our domain are usually rich. -| core.service -| Services provide a reusable part of the applications business logic that is used by multiple use cases. - | 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. +| 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. +| 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 inbound ports and outbound ports. + +| core.port.in +| Inbound ports are the entry of the application. +They provide interfaces that are called from inbound adapters and hide the actual implementation. +A proposal of structuring inbound ports is naming them like single use cases (e.g. CancelReservationPort). +Each port should only provide a single method. +.Design Decision +[%collapsible] +==== +Inbound Ports are not as relevant for the hexagonal architecture as the outbound ports. +Outbound ports are used for the dependency inversion pattern. +For inbound ports could also call the use cases directly. +Therefore, an pragmatic alternative would be leaving out the inbound ports. + +It was decided to include the inbound 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 +| Outbound ports decouple the interaction with external systems. +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. +Outbound 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: @@ -137,4 +190,4 @@ Inside the adapters further packages are differentiating the protocols that is u | Outbound 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 protocols that is used (e.g. `.jpa`). -|=== \ No newline at end of file +|=== From 15756005908d1c17fee438727af8e11e6eb981f7 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 2 Aug 2023 10:15:55 +0200 Subject: [PATCH 05/10] fix: Alter image to fit description - Add inbound ports to the images - make services optional - Remove class names in adapters. This didn't really fit to the rest of the application. It did not become clear that *Listener could be postfixes for class names in java application. Therefore removed it here. - Mor options than client on inbound side, to make the varity of cases visible Ref: #39 --- .../hexagonal_component_architecture_overview.drawio.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg b/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg index ee2b49c..a2c99e3 100644 --- a/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg +++ b/modules/ROOT/images/hexagonal_component_architecture_overview.drawio.svg @@ -1,4 +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>>
Ports
<<interface>>...
Domain
Domain
Mapper
Mapper
Model
Model
Mapper
Mapper
Model
Model
executes
(Not for batch!)
executes...
Client
Client
Persistence
/ Service
/ Components
Persistence...
Services
Services
*Adapter
*Adapter
implements
implements
*Controller/
*Listener/
*BatchExecutor
*Controller/...
calls
calls
calls
calls
Text is not SVG - cannot display
\ No newline at end of file +
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 From 5105035245d530341ade127d6566b7fed18baf68 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 2 Aug 2023 10:40:54 +0200 Subject: [PATCH 06/10] fix: Propose anemic domain models The reason for the anemic domain models lies in two things. First of all are the domain models returned to the adapter. But business logic should not be moved outside. An additional mapping should be avoided. Furthermore in large scaled applications anemic domain models are much easier to handle, because the business logic is not spread across use cases, domain models and services. Making the applicaiton easier to understand --- modules/ROOT/nav.adoc | 1 + .../architecture/hexagonale_architecture.adoc | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) 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 index 8aa9d5e..6a1fde8 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -115,8 +115,7 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a ├── ReservationJpaMapper.java └── TableJpaMapper.java ---- - -[cols="1,1", options="header"] +[cols="20,~", options="header"] |=== | Package | Description @@ -126,7 +125,7 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a | core.domain | The domain circle contains the entities and value objects of the business domain of the application. Related Factories or Builders are located here as well. -The entities in our domain are usually rich. +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. @@ -191,3 +190,29 @@ Inside the adapters further packages are differentiating the protocols that is u That can be database connections, file operations, API calls, message producing and many more. Inside the adapters further packages are differentiating the protocols that is used (e.g. `.jpa`). |=== + + +== 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 with mixed skilled people it's proposed to not use rich domain models. +There are two reasons for this. +First of all, 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. +Furthermore, 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. + +It's proposed to implement the domain model as anemic entities using Java records. +Use 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. From 4c94d9409e135723989ec058ef01423caaeecba9 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 2 Aug 2023 10:49:57 +0200 Subject: [PATCH 07/10] feat: Add VS code folder to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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 From db0914e94fd862fb494ffb2d3d2cbaa7a4b20e36 Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 9 Aug 2023 16:18:33 +0200 Subject: [PATCH 08/10] fix: Remove comments, correct small parts Removed the open comments. Added the multiple monoltih question as a open issue in github #42. Removed the records recommendation, as records cannot be inherited. That might lead to problems with large and complex domain models. Refs: #39 --- .../architecture/hexagonale_architecture.adoc | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 6a1fde8..9a63a48 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -1,18 +1,4 @@ :imagesdir: ../images -// Open Questions/Discussion: -// - Is everything considered in the structure or are there things we cannot handle currently? -// - What about shared technical elements? (AOP for logging across multiple rest adapters) -// - How to organize multiple modules in a modular monolith and what about shared stuff there? -// - Should we really name the adapters after the protocol or more speaking (e.g. reservation for a inbound adapter for reservation) -// - Is structuring core and adapters in packages enough or should we use gradle modules or java modules? - -// Todos: -// - Create ADR (Architecture Design records) -// - Mention that the structure can be made more lean at any time. Consider three case: -// - Complex application with great logic (Probably JPA and entities different, also a great core layer) -// - Application containing logic, but entities and JPA do not differ (potentially a case where the entities in Core can contain JPA specific annotations t avoid mapping) -// - Pure CRUD application (Leave out core) -// - Add an ArchUnit example to make call options explicit for devs (and reusable) = 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. @@ -75,7 +61,6 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a └── application/ ├── config/ │ └── Configuration.java - │ └── Configuration.java ├── core/ │ ├── domain/ │ │ ├── Customer.java @@ -209,7 +194,7 @@ The answer to this problem could be an additional mapping, but this leads to a l Furthermore, 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. -It's proposed to implement the domain model as anemic entities using Java records. +It's proposed to implement the domain model as anemic entities. Use use cases and services to implement the business logic and interact with the domain models. From 09079c5685300d53f59ae2425e102204025154bb Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Wed, 9 Aug 2023 16:30:22 +0200 Subject: [PATCH 09/10] fix: Small change in wording Refs: #82 --- .../pages/architecture/hexagonale_architecture.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 9a63a48..3196fad 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -186,16 +186,16 @@ 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 with mixed skilled people it's proposed to not use rich domain models. -There are two reasons for this. -First of all, the domain objects are returned to the adapters. +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. -Furthermore, adding the business logic to the domain entities spreads it across use cases, entities and services. +. 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. -It's proposed to implement the domain model as anemic entities. -Use use cases and services to implement the business logic and interact with the domain models. +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] From b6519c63e31d388e61db1650877bcc53cd8403aa Mon Sep 17 00:00:00 2001 From: Fabian Baumeister Date: Fri, 8 Sep 2023 11:15:45 +0200 Subject: [PATCH 10/10] fix: Review and align with SEAguide Added review fixes. Renamed all outbound to outgoing and all inbound to incoming (according to SEAguide) --- .../architecture/hexagonale_architecture.adoc | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc index 3196fad..6485090 100644 --- a/modules/ROOT/pages/architecture/hexagonale_architecture.adoc +++ b/modules/ROOT/pages/architecture/hexagonale_architecture.adoc @@ -13,8 +13,6 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a // 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 -// - config -// - Configuration.java // - core // - domain // - Customer.java @@ -59,8 +57,6 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a ---- . └── application/ - ├── config/ - │ └── Configuration.java ├── core/ │ ├── domain/ │ │ ├── Customer.java @@ -108,7 +104,7 @@ image::hexagonal_component_architecture_overview.drawio.svg["devonfw hexagonal a | 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 circle contains the entities and value objects of the business domain of the application. +| 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>> @@ -122,30 +118,30 @@ Depending of the size and adjacency of the use cases a grouping might make sense | 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 inbound ports and outbound ports. +It needs to be distinguished between incoming ports and outgoing ports. | core.port.in -| Inbound ports are the entry of the application. -They provide interfaces that are called from inbound adapters and hide the actual implementation. -A proposal of structuring inbound ports is naming them like single use cases (e.g. CancelReservationPort). +| 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] ==== -Inbound Ports are not as relevant for the hexagonal architecture as the outbound ports. -Outbound ports are used for the dependency inversion pattern. -For inbound ports could also call the use cases directly. -Therefore, an pragmatic alternative would be leaving out the inbound ports. +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 inbound ports nonetheless. They should implement single use cases that are offered. +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 -| Outbound ports decouple the interaction with external systems. +| 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. -Outbound ports should describe the business need for the communication (e.g. StoreReservationPort). How this is then realized depends on the adapter that implements it. +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. @@ -154,7 +150,8 @@ 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. +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: @@ -166,14 +163,14 @@ a| Adapters connect the application core to the surrounding context. They have t * Log the interaction with the surrounding context | adapter.in -| Inbound adapters specify connection points for everything that can trigger the business logic. +| 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 protocols that is used (e.g. `.rest`). +Inside the adapters further packages are differentiating the category of the adapter (e.g. `.web`). | adapter.out -| Outbound adapters define outgoing connections where the application actively interacts with context outside. +| 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 protocols that is used (e.g. `.jpa`). +Inside the adapters further packages are differentiating the category of the adapter (e.g. `.repository`). |===