There are many different styles and interpretations of Domain Driven Design (aka DDD) and the Clean Architecture out there. This example should be a proof of concept how these to concepts can fit together and how DDD can lead us to a clean architecture that focuses on the business (domain). The intention here is not to find the best overall solution for anybody, but to find a good and clear way for me.
The main idea of Domain Driven Design is about understading the appropriate domain. Therefore we'll bringt the domain experts and the developer together to explain the required know-how of a domain to the people that will build the software. A good way to do this is a method called Domain Storytelling. The result will give us a good overview about the actors, the activities and work objects. The separation of the actors and processes can give us a good indicator to find a Bounded Context. One of the most important things is the Ubiquitous Language. This allows us to find the same words in the software that the domain exports uses when telling about their domain
The domain experts - the storrytellers - are telling a story about their domain works to the developers. This story we'll be drawn by a moderator. At the end everybody we'll be able to tell this story and so about the domain workflows by viewing on the drawing.
A wife asks her husband to fix the clogged drain. The husband is a little absent-minded and forgets about it. After a while the wife asks her husband whether to task is done and the husband has to confess that he has forgotton the task. That makes her angry and she gives her husband a complaint. So the husband takes a heart and adds the todo to a todo list. As soon he has a little spare time he wants to get an overview about his undone todos. When the todo is done, he get this todo done. Finally, the wife and her husband are both very happy and still married...
It seems that one good Bounded Context could be the Todo Management
. It should support the husband to getting his todos done.
Because of this example seems to be very simple, we are not having any other Subdomains or Bounded Contexts here. Otherwise the next step would be making up a Context Map.
Let's have a closer look at the operations that the husband performs to the Bounded Context - these are the use cases.
After we identified the Bounded Context. The next step is to get more into that context. Therefore we choosed a method called Event Storming
.
Now it is more clear, what each command needs as input data and which domain events occur.
The domain is the heart of our application and contains the entities, aggregates and value objects. In our example we had already identified the following objects:
- User (known as 'husband')
- Todo
- Todo List
In The tatical design we're defining what the types the domain objects are like. This can be e.g. Aggregates, Entities, Value Objects, Factories, Services or Repositories.
The identified domain objects leads us to the following structure:
We want to follow the Clean Architecture. There are many different diagrams out there, but for me the following one makes it more clear for me.
First, we're implementing the domain objects located in domain
layer. After that we're implementing the Domain Services that have access to the Domain Repositories, etc. These services itselves are stateless and have access to our domain objects and can let them change their states. They are also located in the domain
layer. There are also Ports
(interfaces) that allows us to interact with the environment, like database or an external event bus (see infrastructure layer).
In the application
layer there are only two different types. The first one are interfaces and are called Use Cases
. It describes how an actor would interact with our domain software. The second are Application Services
that are implementing the Use Cases and orchestrating the domain logic.
Until here there is no need are any framework or third party libary. Keep your domain
and your application
layer clean!
In the infrastructure
layer we can now add the (microservice-)framework, like Spring Boot, to get our business application run. Furthermore, we can here implement the required ports from the domain
layer in classes called Adapters
.
If do so, our application is very robust when we're updating dependencies or switching technologies, like database
integration. Then we're just having to rewrite a new adapter and that's it!
Let's see, if our tests runs: ./gradlew clean cJ test accT
We are finding the acceptance tests based on Gherkin & Cucumber under the path src/test-acceptance
. The purpose of these tests is to make sure that the use cases that we implemented in the application
layer working fine.
To run the acceptance tests there is a gradle task under gradle/test-acceptance.gradle
.
We are finding the architecture tests based on ArchUnit under the path src/test-architecture
. The purpose of these tests is to make sure that our source code follows the rules and structure given by DDD and the Onion Architecture.
To run the architecture tests there is a gradle task under gradle/test-architecture.gradle
. These tests will always be executed after the test
task runs.
Purpose | Tool | Link |
---|---|---|
Domain Storrytelling | WPS Domain Storrytelling Modeler | https://www.wps.de/modeler/ |
Creating Diagrams | Draw.io | https://app.diagrams.net/ |
Event Storming | Miro | https://miro.com/ |
UML Modelling | PlantUML | https://plantuml.com/ |
Links:
Topic | Link |
---|---|
Domain Storrytelling Book | https://leanpub.com/domainstorytelling |
DDD Example | https://leasingninja.github.io/ |
Clean Architecture | https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html |