|
| 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