Domain Driven Design — Tactics
Following on from our discussion of high-level design tools, let’s talk about translating our high-level discoveries (what are the domains, events and aggregates) into real code.
If you recall from last time, we’ll be using the persistent example of a classic car auction site. This is primarily a plot device allowing me to add pictures of classic cars :)
DDD distinguishes between value objects and entities.
A value object is something entirely defined by its properties. For example, the letter “A” just is — there can be multiple A’s out there in the world, but whether it’s this one “A” or that one “A” doesn’t make one iota of difference. “A” just is, it’s a value object. It’s also not possible for “A” to change, “A” is immutable. These two properties (immutability, defined by its properties) define value objects.
An entity on the other hand has persistent identity. For example, a Person called Bob is not defined by his name, there are many other Bob’s in the world. Bob’s social security / national insurance number might help to define him but even with that, Bob is mutable. He could change properties (for example change his name) or permanently alter his state (e.g. he could die). Bob is an entity (sorry Bob!).
Why is this important? Well, value objects are conceptually simpler. Entities on the other hand are mutable and thus harder to reason about. In practice, focusing on finding the value objects encourages a design where mutability is the exception rather than the rule. This helps make systems easier to reason about. Focus on the value objects!
Bounded contexts communicate with each other via domain events. Remember, these are simply facts that have happened. If bounded contexts communicate via messaging, then they aren’t coupled. Domain events are statements of facts, they communicate no internal details.
As a side note, it’s worth comparing this to previous attempts. Nowadays, I don’t think this way of communicating is taken as something new, but back in the day there were projects, such as CORBA that assumed that domains could communicate richly (the distributed object model). These models largely failed because of the fallacies of distributed computing. One of the big advantages of messaging (at least when you have at-least-once delivery) is robustness. If the system goes down (if your message broker is reliable) then you can recover.
OK, back to the DDD. In our strategic design, we’ve identified aggregates. Aggregates should be small (say 8–10 concepts) and be a transactional boundary within the system. For our car system, an Auction is an example of an aggregate. It must protect the key business invariants. For example, an auction can only be won by one bid. A new bid must be higher than previous bids (etc). The point of keeping aggregates small is two-fold. Firstly it makes reasoning about invariants easier, and secondly it makes scaling easier. There’s much more (and better described!) descriptions about aggregates by Vaughn Vernon.
Behaviour should belong on the domain objects (entities or values). Why is this? Well, DDD is classic object-oriented and in that model behaviour belongs with the objects.
The opposite of this is the anemic domain model. This is an anti-pattern akin to procedural code. Entities/Values become little more than structs with no behaviour. Domain logic glues things together in static methods. Avoid this!
For behaviour that doesn’t belong to the domain (think means to an end, like sending a text message or dispatching an email), domain services come into play. A service should be stateless, relate to a concept that is NOT a natural part of an entity or value object and be defined in terms of an interface.
There’s obviously an infinite number of ways of implementing a domain-driven design. The point of using these tactics is that they are:
- A shared language for the implementation team(no need to come up with a novel implementation)
- Proven in real-world architectures, with heuristics to guide you
- Preserve the domain model
When I was a more junior developer, I used to worry that tactics like these would take the spontaneity out of development — wouldn’t it be much more fun to implement everything from first principles?
As I’ve gained more experience my values have changed. Most software projects aren’t novel; the challenge is organising software around the problem that’s being solved so you can easily reason about your software. DDD is one of the ways of doing that.
In the final part of this series, we’ll look at higher level patterns for DDD architectures such as CQRS and event-sourcing.