A while ago a took the “Implementing DDD” course with no less then Vaughn Vernon (@VaughnVernon) and Patrik Fredriksson (@weakreference) at Citerus. Vernon has written the book with the same name as the course.
Here is my notes from that course.
Disclaimer: This is my notes and my interpretation of the information. I can really recommend going the course.
Part 1: Introduction
The expectations was high and after a short round robin introduction of the participants we were ready to go.
Vernon presented what will be in the course by taken a short introduction to Aggregates, Repositories, Services, Domain events, Entities and Value objects.
We were going to hear about how we can integrate the different context with each-other without increasing the complexity. How to use model naming strategies when modelling it by the ubiquitous language.
For those who was unfamiliar with domain events, since it’s not explicit mentioned in [Evans] book, here is a short introduction.
Domain events are created by the aggregate and published to the event publisher that delivers it to the subscribers. This can be seen as a publish-subscriber pattern or observer.
Benefits with domain events:
- Domain events can be used to keep a record of what have happen in the domain.
- Domain events can also ease the integration between different (sub-) domains.
Part 2: Bounded Context and the Ubiquitous language
This is DDD. The bounded context and the Ubiquitous language is the most important part of DDD. The thing to take with you from this day is the Bounded Context and the Ubiquitous language. It is the foundation of DDD.
Observe that expert thinks of the domain and usage while developers tend to thinks of code and technical stuff. So have a feedback loop between the developer and the expert.
Remember to always involve the Experts!
So what can be inside a bounded context?
- Domain model
- Service interfaces: RPC (SOAP), Rest, Messaging
- User interface components
- Application services/command handler
- Database schemas
The word domain is somewhat overloaded (which domain are we talking about). The Domain in the whole business that is helping the business forward. The parts that builds up the domain are sub-domains.
To map the domain you must start with defining the problem space and solution space. The problem space is the context, and the solution space is the domain.
Start with defining what your core domain is. The Core domain is the sweet-spot of our system. The core is often supported by sub-domains. But your core domain can be another teams sub-domain. So there can be different perspective in the view of the domain between different teams.
Remember that multiple sub-domains can belong to the same bounded context. But try to only have one bounded context per (sub-) domain.
Try to keep your core domain as clean as possible using the single responsibility principle. Try to identify possible sub-domains that can be moved out of the core domain. This can be done using linguistics to identify sub-domains (terms that do not fit in the current core domain).
Always remember that context is King! You need to know your context to know the meaning if the model.
To handle legacy system aka BBoM (Big Ball of Mud). Try to map sub-domains to the BBoM (Big Ball of Mud) to visualize what’s there. This is a great way to visual the vision of how we want the end result to be (even if it’s not possible to reach in polynomial time).
Part 3: Context mapping
Use context mapping to describe the relation between different contexts. Especially defining what are upstream and downstream in the relation. Upstream serve the downstream, think of it as a river metaphor, making the downstream dependent of what the upstream produces and offers.
- Upstream and downstream dependencies are equal
- No one can create value on there own
- If one fail all fails
- A shard (sub-) domain between different teams
- Needed by more the one team
- Hard to maintain
- Upstream can create value of there own
- Upstream supplies the downstream
- Downstream must request changes from the upstream team
- Downstream must conform to upstream changes => conformist or different ways
- Downstream must conform to upstream changes and upstream leaves the downstream helpless
- Translate other system model to match your needs
- Isolate your domain to prevent leaking of other domains
- A protocol published to let you talk to other services
- A common translation between two (or more) bounded contexts
- Often combined with an Open-Host service
- Create your own model and leave the other context/sub-domain
Big Ball of Mud
- The name says it, stay away from it if possible
Part 4: Architecture
- Classic architectural styles
- Enduring architecture
Using the dependency inversion principle to manage the call hierarchy and loosen the coupling to other parts such as storage. This can be done by defining the persistence storage access as interface in the domain and placing the actual implementation on top of everything by using DI.
Hexagonal (Ports and adapters)
Define input and output ports to communicate on. Use one port per client type and let the application layer handle the conversion and calls to the domain services.
Separate query and command (view and action). Making every command doing exactly one thing. Retrieve the new view to see the changes making the model having separated write and read. The command should never change the state directly of the part that is retread when reading.
Pipes and filters
Look at it as pipes in *nix systems. Where every command is a filter that changing the data and every pipe forwards the result between the commands. In the same way we can implement a system where every filter do computations on the data and the pipes sends it to the next filter.
Messaging between aggregates (See Akka (JVM), Scala (JVM) or ActorFX (MS)).
Think of it the internet where every computer is an actor. Every actor can communicate with every actor. The actor state can be recreated form all events that it has received and the events are persisted when the event is received. This makes the model robust when the actor can be recreated at any time to receive a new event.
Quote of the day
In short, code what you mean.
Tell, don’t ask objects what to do.
Use roll-based access to the system.
Part 5: Entities
- Using DDD tactical design
- Avoid Entity-think and entity overuse
- Unique identity and construction
- Designing intrinsic qualities and characteristics
Use entities when:
- An identified instance may change over time
Entity-think = bad
Don’t think of every thing as an entity
- User provides an identity (must ensure its uniqueness)
- Application generates the unique identity
- Persistence store generates an unique identity
- Another bounded context provides the unique identity
Order can matter when the entity gets the unique identity.
The identity is provided by the user or application. It can be managed with a surrogate identity to prevent inconsistency in the persistent storage.
The identity is given to the entity when it is stored in the persistence storage.
- Validate properties in setters.
- Validate whole object can be done using a special validator eg. ProductValidator.
- Validate deferred may involve other objects eg. single object is fine but the whole model fails due to some constraint.
Part 6: Value Objects
- DDD Tactical design prefers value objects over entities
- The common characteristics of value objects
- Integrate with minimalism and design standard types
- Persistence considerations
Value-think = Good
Choosing between a Value object or entity depends of the context but favour Value Object over Entities.
Immutable and Replaceable
Private setters for validation, public “getter” but ignore the get part and express the getter with the ubiquitous language.
Use methods for modifications but return a new object instead such as the Javas String substring method.
Part 7: Services
- Domain services are not SOA or Application services
- When to model a Domain service
- Sample Domain services
Domain services may
- Dispatch to the model
- Be double-dispatched by and from the model
Domain services in the architecture
Domain services is
- Stateless operation
- Not transactional
- No natural operation home
- Force-fit on entity
- Wants to be static
Part 8: Domain events
- Collecting the facts about your domain by use of events
- Publish events in your application and beyond
- Modelling events and event-driven modelling
When and way
- If it happens
- Notify me
The end of Batch? It can reduce the batch window when we gets notifications of when things happens in the system.
- Acronym for Publish Subscribe
From the events that are handled you can create a glossary over the events.
|Bounded Context||Event Name||Origin||Description|
|Authorization||Login||Authentication||Dispatched when a user log-in into the system.|
Checks pattern language by Ward Cunningham
Part 9: Modules
- Modules do’s and don’t – Practical guidelines
- High level modules and modules in the model
- Other areas of the application
Simplest form Java packages and C# namespaces
- Design modules to fit modelling concepts
- Typically a model for one or a few aggregates that are cohesive, if only by reference
- Name modules per the ubiquitous language
- Design loosely coupled modules
- Strive for acyclic dependencies between modules
- Relax the rules a bit between child and parent modules
- Create modules mechanically according to a general component type of pattern
- Make modules a static concept of the model, but allow the to moiled with the objects
The name of the module should be something useful but not to long
<< service >>
<< aggregate root >>
<< aggregate root >>
<< aggregate root >>
<< aggregate root >>
Other areas of the application
com.sassovation.agilepm.resources // Rest
com.sassovation.agilepm.resources.view // UI
com.sassovation.agilepm.application.product // Application services and commands
Modules before creating a new bounded context
Part 10: Aggregates
- Part 1: Common pitfalls of aggregate design
- Part 2: Follow the rules of aggregate domain (mostly) design
- Part 3: Reimplementing the BacklogItem design
Every aggregate have a root entity. The root entity must have global unique identifier.
By design every aggregate must be transactional.
Part 1 – Common pitfalls
- Focus on object graphs and navigational convenience
- Imagine incorrect invariants (consistency constraints)
- Tune persistence to support previous two pitfalls
Positive: Navigational convenience for model client
Negative: Transactional failures
Negative: Size and performance
Invariants say: When this object changes that object must also change.
Imagine false business constraints
Aggregate design are influenced by wrong assumptions often lead to large-cluster aggregate problems.
Force persistence mapping to fit large-cluster aggregates. Enabling large-cluster aggregates leads to some issues still leads to performance and scalability issues.
Aggregate boundaries are for transactional reasons. So persisting a aggregate is a atomic action.
Rule: Model true invariants in consistency boundaries.
Aggregate boundaries for invariants
Rule: Design small aggregates
Use Eventual consistency to manage a situation where an aggregate might affect another aggregate.
How many aggregates are there in this picure?
Rule: Reference by identity
Should aggregates use repositories?
Rule of thumb: No.
Rule: Use eventual consistency outside the boundary [Evans, p128]
Ask who’s job it is
Transactional or eventual consistency?
Is it the job of the user – Transactional
Is it the job of another user or system – Eventual consistency
Reason to break the rules
- User interface convenience
- Lack of technical mechanisms
- Global transactions
- Query performance
Adhere to the rules and only break them with a good reason.
Model the BacklogItem in the sassovation agilepm application. See the book.
Part 11: Factories
Motivations for using factories in the model. See the book.
Part 12: Repositories
- Collection oriented repositories
- Persistence oriented repositories
- Look at sample repositories
Collection oriented eg. java collection (Set). Can be used with a document based database eg. MongoDB.
Use one repository per aggregate. And try to not call repositories from aggregate, use a domain service instead.
Part 13: Integrate the bounded context
If all context provides an open host service through eg. REST it is easy to integrate other context with that one using a small anti-corruption layer.
See the book and look at the example code for more information.
Part 14: Application
Designing the application with regards to
- Application without architecture
- User Interface
- Application services
Common things to think about.
- Render DTO from aggregate instance
- Use a mediator to publish aggregate internal state
- Render aggregate instance from domain payload object
- State representation of aggregate instance
- Presentation model
- Use-case and optimal query (CQRS)
- Using commands
Workshop: Walk-through of parts of the example code
Idempotent = Side effect free (wikipedia).
To implement a Event Sourcing model (to be extended as an actor model). How must I store the events to let them recreate the state of the aggregates?
You have to store each event together with the id for the root entity for the aggregate. Use this id as a key to retrieve all the events that has affected the aggregate.