diff --git a/README.md b/README.md index c25ba3e..92d79c3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ Most of the information on how to design in this way are from the following book [Implementing Domain-Driven Design | Vernon Vaughn](https://www.amazon.com/-/de/dp/B00BCLEBN8/ref=sr_1_1?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=E6QQJD59WB0U&dchild=1&keywords=implementing+domain-driven+design&qid=1618152014&sprefix=implementing+d%2Caps%2C237&sr=8-1) +### Prerequisites + +* Java 11 +* Maven +* Docker or mongoDB + ### How to run To run this application you need to first run a @@ -18,7 +24,7 @@ To run this application you need to first run a mvn clean ``` -in the root directory of this project. This will download all required dependecies and bundle your +in the root directory of this project. This will download all required dependencies and bundle your project properly. It will also auto generate some mapping classes with the [mapstruct](https://mapstruct.org/) plugin. @@ -29,8 +35,6 @@ or you can alternatively start the MongoDB in docker with the included docker-co docker-compose -f mongodb-docker-compose up ``` -Docker / docker-compose need to be installed for this. - The application can then be started by running the main function in: ``` @@ -42,7 +46,7 @@ hexagonal-example\config\src\main\java\de\strasser\peter\hexagonal\HexagonalAppl This app can register customers, add addresses to these customers and retrieve a list of all customers. -The supported usecases in the business layer can be inspected in the applicationmodule. Under +The supported use cases in the business layer can be inspected in the application module. Under application/src/main/java/de/strasser/peter/hexagonal/application/customer/port all the supported operations are clearly visible, which is a major selling point to structure your application in this way. @@ -52,9 +56,9 @@ way. ### Application architecture This application tries to follow the design princples as described in Clean Architecture. To achieve -key characteristics of a good architecture (Single Responsibility, Flexibility, Testability, ... ), -one very defining lession of Robert C. Martins book is that the business logik should be at the core -of the system. +key characteristics of a good architecture (S.O.L.I.D., Flexibility, Testability, ... ), one very +defining lesson of Robert C. Martins book is that the business logic should be at the core of the +system. ![circles](documentation/circles.png) @@ -69,9 +73,7 @@ but certainly not as exciting sounding as a hexagon. ![hexagonal](documentation/Hexagonal.png) In the middle lives the **Domain**. This is where all the business logik is contained. Ideally this -code is free of any framework specific annotations. This is especially great for unit testing your -logik. No spring context, that has to boot, or any services that need to be mocked. Just plain -normal Java, which is easy to understand and fast to execute. +code is free of any framework specific annotations. Ecapsulating these Domain models are the **Use Cases**. These represent the features that our application provides. They receive commands and query / write the data to the adapters. @@ -82,10 +84,6 @@ adapters implement the output ports and therefore have to follow the contract, t application defines for them. The input adapters only interact with the input ports and never with the implementing serivces directly. -This clear separation of concerns greatly increases flexibility. If you wanted to switch from an SQL -to NoSQL DB, you'd just have to rewrite your Persistence Adapter, but the rest of the application -should not be affected by your change. - To achieve true decoupling between the application components the services are not allowed to construct instances of objects themselves. If a use case class would construct the instance of an output adapter in its class it would introduce dependencies into that layer, which we do not want. @@ -105,7 +103,9 @@ configuration component is the component, where the Spring main function lives. ![dependency diagram](documentation/dependencies.png) -### Advantages of separated models on each layer in this example +### Advantages of separated models on each layer + +Some Examples: - The customer response can easily exclude the (hashed) password without much effort. This separates the concern in what way to display the data to the client. @@ -119,5 +119,66 @@ configuration component is the component, where the Spring main function lives. how to handle data persistence is independent of the business layer and can be handled by the persistence module. -- The Command models can validate the input to the business logik and make sure nothing + +- The Command models can validate the input to the business logic and make sure nothing unprocessable enters the services. + +What we can learn from these examples is that, even though it might seem very redundant and tedious +work at first, every model has +a [specific job](https://en.wikipedia.org/wiki/Single-responsibility_principle) and is there for a +reason. + +The overhead from writing more code in the beginning is offset by the fact that the code is much +more readable and maintainable. + +### Advantages of the domain models in the center of the code + +With the domain models being free of any framework specific annotations this is especially great for +unit testing your business logic. No spring context, that has to boot, no proxy classes, or any +services that need to be mocked. + +Just plain old Java, which is easy to understand and fast to execute. + +### Advantages of the adapters + +One great benefit is that we comply with +the [Single Responsibility Principle](https://en.wikipedia.org/wiki/SOLID). Each adapter does only +the thing it is supposed to do. The web adapter for example only handles incoming web requests and +nothing else. + +The clear separation of concerns also greatly increases flexibility. If you wanted to switch from an +SQL to NoSQL DB, you'd just have to rewrite your Persistence Adapter, but the rest of the +application should not be affected by your change. + +### Advantages of the ports + +The ports help us comply to +the [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) +and +the [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle). + +Between the adapters and the use case services the ports serve as a layer of abstraction. By +defining a port interface for every use case that we have, we can make sure that no client (or +adapter in this case) has to depend on methods that it does not use. + +By defining the port interfaces we can force the adapters to comply to the rules of the application +/ business module. This is known as dependency inversion. + +![dependency](documentation/dependency.png) + +Without the interface the RegisterCustomerService / application module depends on the CustomerDao. + +![dependency](documentation/dependency_inversion.png) + +With the interface the CustomerDao depends on the Application module. + +### Disadvantages of this architecture + +This style of implementing a service has alot of overhead. For a simple CRUD application or any +application without much business logic this approach, would certainly be overkill and would result +in alot of tedious mapping without much benefit. + +### So when should I use this architecture? + +This architecture really shines in an environment with a lot of business logic and is *very* well +suited to implement [Domain Driven Design](https://de.wikipedia.org/wiki/Domain-driven_Design). diff --git a/documentation/dependency.png b/documentation/dependency.png new file mode 100644 index 0000000..5400358 Binary files /dev/null and b/documentation/dependency.png differ diff --git a/documentation/dependency_inversion.png b/documentation/dependency_inversion.png new file mode 100644 index 0000000..ca2f13b Binary files /dev/null and b/documentation/dependency_inversion.png differ diff --git a/documentation/drawio/dependency_inversion.drawio b/documentation/drawio/dependency_inversion.drawio new file mode 100644 index 0000000..6924ae3 --- /dev/null +++ b/documentation/drawio/dependency_inversion.drawio @@ -0,0 +1 @@ +zVZNj5swEP01OW4VIBByzCbb9tBVq43UpqfKsQewajAyJpD++o6DCZ+rbaVU6imeN57xzHuDnYW3S+sPiuTJs2QgFu6S1Qtvv3BdZ7Nx8ccgF4s43rpBYsWZxTrgwH+BBZcWLTmDYrBRSyk0z4cglVkGVA8wopSshtsiKYan5iSGCXCgREzRb5zppEFDd93hH4HHSXuyE2waT0razbaTIiFMVj3Ie1p4OyWlblZpvQNh2Gt5aeLev+K9FaYg038SQA9fn48VzT+nwY/T5fHysHlxHmwbZyJK2/A2zwWnRHOZ2br1pSUDW8jNMpWsNNgjETzOEBAQYQ2PRU4oz+JPV2uP9XQ7KFYJCoEzKI35xdY6tMwRjbgQOymkuh7kMR9CtjIZtZI/oecJ3ZMXBOixhWM2qF9lxLnxjBMKMgWtLrilDfCsNHY4vXboqk5pz7dY0lPZXVqQ2OmKb7k7AXBhNfgLPZyJHi8Q8wKp25WFxkPUAdSZU5hoo2SZMTC5De1VwjUcUA7jrfDbRCzRKdayd6Z0R1HkUjpHNwtOgX8vutcjugN/Qre7nqF786/Ydidstyzvibwrw4xAGM0yHNAQTtF9GPb99X/GsDdheEIrZGxrLmpzSQhSFJwOmYSa62Nv/d0I8M631r62elyNS2tkWPyxb/SijNmFXa02rikO2ORNGCmADchSUXj7S9ZExaDfmsGpoj3F5m6gFlMg8Ko+D8udU9Ge8EVybOQ2MCt/+EmuwtEgNG3aqP7jMkrkBqNEzihRw8Mk0XWobm3PzRma3RvZbO/+anhPvwE= \ No newline at end of file