I. Introduction
Domain Driven Design contains the core idea that you develop a software to resolve a problem that exist in a defined domain. Understanding this domain is the key to your success as a software provider. This domain is usually understood by so called domain experts that basically will use your software to simplify their daily work.
I think DDD is mostly useful when you need to build a solution that is pretty complex and cross domains. The interesting thing about DDD is, for it to be successful, it will also influence your Organisation.
II. Domain Driven Design
a. Knowledge Crunching
Knowledge crunching consist of getting / extraction as much information as possible from the domain experts. To do so, several methods exist:
- Domain Story telling:
- Like the title explain it, we basically ask the domain experts to talk about their daily work in scenarios and document it as domain stories.
- Pro/con:
- I would basically use it when there are story to actually tell, or the defined person knows how this entire story is. The reason is that maybe a process is separated in different steps and different domain experts have different point of view what they need and how they do it. Ex: I get something from there and do something with it. But I do not know if this is the final step or not.
- Useful free tooling would be: http://domainstorytelling.org/
- Whirtpool:
- Whirlpool describe a process where, as software expert, you define a set of scenarios with the domain experts, create than a fast model, than a fast prototype and then challenge them with the domain experts again. The output of the challenge, brings us back to point 1: define new set of scenarios, …
The key goal is generate as much discussions with the domain experts before you implement the final solution.
It is mostly to compare to extreme programing and TDD.
- Pro/con:
- Very time consuming for both side, but has the advantage to be "agile" and learn a lot about the domain. It may not be applicable when the solution is to big.
- Useful link: http://domainlanguage.com/ddd/whirlpool/
- Event storming:
- Define all possible events that can occur in a domain and cluster them in contexts. It is called: drawing a "big picture". Afterwards, you have to do a "strategic event storming". In this step, you document what are the commands for these events, who are the actors, and later on put them in aggregates.
- Pro/con:
- You need to do a workshop with all domain experts that cover with their knowhow the entire domain. It can be fun to do it, it just costs a lot of time for all of them to be present. If the workshop fails (ex: the domain experts found it useless), it may have direct consequences on your project.
- Useful link: https://en.wikipedia.org/wiki/Event_storming
b. Tactical Design with Bounded Contexts
Tactical Design is used to be able to build different models to represent the same element / domain. The idea is through different aspects and models, I can overcome the complexity of the domain.
Therefore, if I have a model, it needs to have explicit boundaries to understand when I should refer to it or to another one. We call such models, bounded contexts.
Information transiting between two models are called Domain events and can be in the implementation compared to pipes in the pipe & filter architecture pattern.
To cut your domain in different bounded contexts, you can look at where are the boundaries in the processes, in the company structure, in the different domain experts, in the subtleties of a same element are. You can use the following method to find out:
- Domain Story telling collection:
- After collecting a big number of stories of different domain experts, you can
- identify which entity are part of the process(es)
- A story has contains usually a point of view from the domain expert who explained the story. It could describe a specific bounded context.
- After collecting a big number of stories of different domain experts, you can
- Event storming output end discussion:
- After the domain experts finished describing by events their domain, you should automatically have events clustering on some places of the board. A cluster probably gives you a hint, it may be a bounded context. There must also be events that create a transition to another bounded context. With a pen, you can cut the board in different bounded context. Include the domain experts when you cut it! They will help you cutting it but also help you finding correct names for these contexts!
c. Tactical Design with Subdomains
To continue on our structuration of a specific domain, we can cluster one or many bounded contexts within a subdomain (the other way around is strictly forbidden!). A subdomain is a partial representation of a domain.
Tactical design with subdomains has the following base: Not all of a large system will be well-designed! Therefore, we need to prioritize which domain or subdomain needs to be clean and well designed and which not. To do so we need to tag the different domains / subdomains with:
- Core (sub)Domain:
- Business domain
- Domain where the success of the company directly depend on
- Domain that allows the company to differentiate itself with the concurrences. (USP)
- Ex: eCommerce: referencing strategy in Google
- Supporting (sub)Domain:
- Domain providing the set of needed information for the core domain to be fully functional
- Does not contain USPs, but is still specific to our domain
- Ex: eCommerce: statistic information gathering for the referencing strategy
- Generic (sub)Domain:
- Domain needed for the full solution to be operational
- Does not contain any USPs, is even not specific to our domain. It can probably be bought.
- Ex: eCommerce: usage of the google analytics platform
Parentheses:
- Kano-model: allow us to remember what is important for the customer and what should be clean and well designed, and what not.
- Brownfield Strategic Design: how to clean-up or refactor an existing system which is monolithic (also called sometimes: Big Ball of Mud)
- Identify the different subdomains and their domain terms in the monolith
- Duplicate code of one subdomain and eliminate all functionalities that does not belong in
- Build a small interface for this subdomain to the rest of the source code
- Loop
After having a so called "Modulith", we can separate the different modules into microservices if needed.
d. Context mapping
When we have a clear representation of what our sub-domains are and our bounded contexts, we need to identify / define the link between the different bounded contexts. This is called context mapping.
After linking the different contexts, you need to define the type of relation they will have. The following "collaboration models" exist:
- Partnership:
- The different contexts responsible define together the interface to have between the 2 contexts.
- It means: regular sync meetings, CI, high synchronization effort needed.
- Separate ways:
- The different contexts responsible do not think working together generate enough value and decide to implemented themselves the needed elements from the other context.
- It means: no sync, everybody has its own specific solution.
- Upstream-Downstream:
- Customer-Supplier:
- The upstream, supplier-team implement the needed services
- The downstream, customer-team use the services and influence also how the service should look like
- Conformist:
- The upstream, conformist, has a set of services defined, conform to all their customers
- The downstream, customer use the services and has to live with the change decisions of the conformist
- Ex: SAP system, AWS micro-services, DOORS… you have to live with what they propose and cannot really influence the service API.
- Customer-Supplier:
Technically, the following designs exists to implement the decided "collaboration model":
- Private APIs:
- The bounded context has its interfaces declared in house and are only accessible of the access is granted…
- Open-Host-Service:
- The bounded context defined a common protocol or interface for the usage of their services. Everything is clear documented and always accessible.
- Shared Kernel:
- The two bounded contexts agreed to have shared element(s) used in both context and maintained by both. High synchronization is needed here.
- Ex: In the Bank industry, the IBAN could be one shared element.
On the defined, used interface, you can add another design model to define the content that transit:
- Published Language:
- Name given to a clear API documentation. Usually the documentation is written with a state of the art tool.
- Ex: Doxygen, REST, JSON schema, Protobuf,…
- Anti-Corruption Layer:
- Name given to adaption / wrapping of services providing by another bounded context.
- You would basically use it to remove direct dependencies to conformist services.
e. Ubiquitous Language
The key idea here is:
- To learn to speak the same language as the domain experts.
- Do not bring any software technical language in the discussions.
- Reflect in your software the domain knowhow by naming the classes, functions with the specific domain name for these objects or actions.
- Write a glossary to document the wordings.
Each bounded context has its own ubiquitous language.
The domain specific words are usually contained in the output of an event storming or domain story telling.
Remark: When you do not know how you can resolve a problem technically, involve the domain expert for a workaround!!
f. Domain Model Patterns
A Domain model pattern will be basically used to define the design within a bounded context. There are really many possible pattern here. Some are easier to understand than others:
Layer architecture pattern:
Everybody know what it is.
- Idea: UX layer --> Application layer --> Domain layer --> Infrastructure layer
Fowler's domain logic pattern:
Composed by:
- Transaction scripts:
- Describe and execute and persist a (domain) transaction.
- Fast to implement but if we end up with 200 scripts, maintainability starts to be very difficult.
- Table modules:
- Domain logic embedded in a class to handle a specific database table.
- Domain models:
- Simple OO-modelling.
Remark: Domain named entities with no domain behaviour (ex: Java Beans) has the consequence to build an anaemic domain model. This makes the maintainability and logic worst.
Cockburn's hexagon architecture pattern:
It mostly looks like an onion,
- In the center, we have the domain specific knowhow.
- After we have the ports layer
- After we have the adaption layer
- Link: https://declara.com/content/va7eLmgJ (The original site is under construction…)
Uncle Bob's clean architecture pattern:
Similar to the previous pattern with the following ideas:
- In the center, we have the Entities --> Enterprise Business Rules / Domain knowhow
- After we have the Use Cases --> Application Business Rules
- After we have the Controllers / GWs / Presenters --> Interface Adapters
- After we have the Devices / Web / UI / DB / External Interfaces --> Frameworks & Devices
Some images can be found in: https://www.entropywins.wtf/blog/2018/08/14/clean-architecture-bounded-contexts/
Remark: Each API applied in the DDD should follow the CQS (Command-Query-Separation) Principle from Bertrand Meyer. A method has an action, it is a command. A method read something, it is a query. Mixing both should be forbidden! It reduce readability…
g. Tactical Design with Building Blocks
After having defined the design pattern for the bonded context, we need to define what does the "core" contains. We have here a slightly different building blocks structure compared to the traditional UML ones:
- Entities:
- Object existing in the bounded context (shall have the right domain name)
- Example: a pen, a mobile phone,… There are unique objects.
- Object values:
- It is not an object… It is a value with some information about the value.
- It is an immutable object. It can contain some functions, but the output will always be the same.
- Ex: a temperature -> attribute: raw_value, methods: get_C(), get_F(), get_K()
- Aggregates:
- Group of entities & object values represented by a root-entity.
- When an application need something, it has to communicate via the root-entity.
- This way, you protect yourself from consistency and integrity issues.
- Repositories:
- A repository is a collection of aggregates.
- It provides an access point to the aggregates (we are talking here also from persistence)
- Services:
- Contains behaviours /transition states for the above elements.
- Ex: If you have two aggregates that depends on each other: an action/result from one, needs to trigger something to the other one. Than you build a service on top.
- Factories:
- When you need to generate and initialize complex aggregates (including what they contain), a factory is the solution. By initializing, it ensure that the content is consistent (persistency included)
There are also some small rules that needs to be taken care:
- Entities can contain other entities and object values.
- Object values are forbidden to contain entities! But they can contain other object values.
- When an aggregate exist, you are only allowed to use the root-entity. Accessing internal entities is forbidden…
- … With one exception, a factory, in case of complex aggregates. For simple elements, you do not need a factory!
Please do not forget to consider the CQS Principle (see above)
III. Additional Details
a. Event Sourcing
Name given to the mechanism to persist:
- Aggregates as a whole
- Events occurring in the system (results, status changes,…).
b. CQRS (Command-Query-Responsibility-Segregation)
It is a friend of the CQS-principle.
The idea is instead of having within one Entity, methods regarding queries and methods regarding commands, you create two entities, one for the queries, one for the commands.
It can be interesting if you
- Have two different (data) models for commands & queries.
- Want to create a command store containing all commands.
c. Microservices
A micro-service is an encapsulation of a service into a unit that:
- Possess its own data structure
- Can be deployed alone
- Possess its own process or VM
- Uses a standardised communication infrastructure to access 3rd and to be accessible (REST)
To link to the DDD, a microservice shall be/represent exactly one bounded context.
Remark: Micro-service or not, for a bounded context, II. f. and II. g. applies for its architecture & design.
d. WAM (Werkzeug, Automat, Material)
This is a German concept that existed before the DDD but did not become popular enough. The only big difference with the DDD is about the Type of entities:
- Werkzeug:
- Entity that work, transform material
- Active entity
- Material:
- Entity that can be worked on.
- Passive entity
Link: https://de.wikipedia.org/wiki/Werkzeug-_und_Materialansatz
IV. Summary
- DDD is applicable on big systems.
- When we start a DDD from scratch, for it to be deployed successfully, we will have an inverted Conways law: The architecture is reflected into the organisation.
- Have fun 🙂