A 36-minute working session on taming complex business software — from the ubiquitous language and bounded contexts, through aggregates and domain events, to the strategic call of when DDD is worth it and when a plain CRUD app quietly wins.
Most systems don't collapse under load; they collapse under misunderstanding. The team ships features fast for a year, then every change starts breaking three things nobody connected. The root cause is almost always the same: the code stopped matching how the business actually works. DDD is a set of tools for keeping those two in sync.
When rules scatter, every change is a hunt. DDD pulls the business logic into one well-named model.
Devs and the business use different words for the same thing — a "user" in code, a "policyholder" in the meeting. Translation errors pile up silently.
Logic ends up smeared across controllers, queries, and triggers. Onboarding takes months; small changes feel dangerous.
Invest in a shared model and language up front. It only pays off when the domain is genuinely complex — which is the honest catch we'll return to in Part 7.
The single highest-leverage idea in DDD costs nothing to start: agree on the words. When the domain expert says "a quote expires", the code should have an expire() method on a Quote — not a status flag flipped by an update handler. Same word, same concept, everywhere.
Every silent translation between business and code is a place bugs and misunderstandings breed. The shared term removes the gap.
Like a glossary the whole team signs: when everyone — sales, support, engineering — means exactly the same thing by "shipped", the arguments stop being about words and start being about the work.
Try to make a single Customerclass serve sales, support, and billing and you get a monster with forty fields that's wrong for all three. The fix isn't a bigger model — it's several smaller ones, each correct inside its own boundary. A word is only allowed one meaning per context.
"Customer" is a different model in each context. Forcing them into one shared class is the classic mistake.
Once you have multiple contexts, you map how they relate. A context map is the big-picture diagram of your contexts and the integration relationship on each boundary — who depends on whom, and who translates.
This is where DDD meets system architecture: a bounded context is the natural seam to split a service along. The mechanics of those splits live in Architecture Patterns & Styles.
A context map: arrows point upstream → downstream (U→D). Identity is a shared kernel both depend on.
Partnership — two teams coordinate closely and evolve their contexts in lockstep. Shared kernel — they share a small, jointly-owned slice of model (e.g. a common Money or UserId). Powerful but expensive: any change needs both teams to agree, so keep the shared part tiny.
The upstream context provides something the downstreamone needs (Sales feeds Billing). It's a healthy relationship when the downstream team's needs are negotiated into the upstream team's backlog — supplier serves customer.
When the upstream won't bend — a payment processor, a giant internal platform — the downstream simply conforms to its model. Cheap and pragmatic, but you inherit their language and their quirks. Fine for generic concerns; risky for your core domain.
When you must integrate with a messy or foreign model but refuse to let it leak in, you build an anti-corruption layer— a translation boundary that converts their concepts into yours. We give it real code in Part 6; it's the most valuable defensive pattern in DDD.
Inside a bounded context, the model is made of three kinds of object. Get these distinctions right and your invariants — the rules that must always hold — enforce themselves. Get them wrong and you're back to scattered validation and corrupt data.
Has a unique id that persists through change. Two orders with identical contents are still different orders. Think Order, Customer, Shipment.
No identity, immutable, interchangeable. Two Money(5, "USD") are equal and swappable. Think Money, Address, DateRange.
A cluster of entities and value objects treated as one unit, with a single root as the only entrance. Rules inside it stay true together.
Outside code talks only to the root. Lines and Money are guarded inside; other aggregates are referenced by id, not held directly.
Like a shopping cart: you add and remove items through the cart, never by editing a stranger's line item directly. The cart is the root; it keeps the total honest.
Businesses think in events: an order was placed, a payment was received, a shipment was dispatched. Making those facts first-class in your model — as domain events — keeps your aggregates small and your contexts loosely coupled, because reactions move out of the aggregate that caused them.
OrderPlaced, PaymentReceived). The aggregate that owns the change records it; other parts of the system subscribe and react — often in another bounded context.The aggregate records the fact; subscribers react. The transport — a broker — is a separate concern.
Domain events are a modeling idea; how they travel between services — brokers, partitions, delivery guarantees — is the subject of Message Queues & Streaming. A domain event inside one process can simply be an in-memory notification.
Event storming is a fast, low-tech workshop: get domain experts and developers around a wall and map the business as a timeline of events on sticky notes. Orange notes are events; blue are the commands that cause them; yellow are the aggregates they land on; pink are external systems. In an afternoon you discover the real process, the language, and — crucially — where the bounded-context seams fall.
Command → aggregate → event, left to right on a timeline. Colors map note types; the gaps reveal context boundaries.
Strategic design draws the boundaries; tactical patterns implement them. Three earn their keep on almost every DDD project — they keep persistence, cross-aggregate logic, and foreign models from leaking into your clean domain.
A collection-like interface that loads and saves an aggregate as one unit. The domain speaks OrderRepo — never SQL or an ORM.
When a rule spans several aggregates and belongs to none, put it in a stateless domain service — named in the ubiquitous language, not a junk-drawer Manager.
A boundary that converts a foreign or legacy model into yours, so their concepts never pollute your domain.
The anti-corruption layer converts the legacy CRM's shape into clean domain terms — its quirks never cross the border.
The repository is dependency inversion applied to your data layer; the ACL is the Adapter pattern guarding a whole context. DDD reuses the same class-level tools you already know — it just points them at the domain.
DDD is a modeling discipline, not a library you import. These are the places it touches the rest of your toolchain — pick by what you actually need, and remember most projects need none of the heavy ones.
The real work is conversation. These just give the workshop a surface.
Pro — infinite shared canvas for remote event storming; sticky notes, voting, templates.
Con — paid seats; can sprawl without facilitation.
Choose for distributed teams running recurring workshops.
Pro — free, instant, low-ceremony; perfect for a quick context map or first storm.
Con — fewer workshop features (voting, timers, big templates).
Choose for lightweight sketching and ad-hoc diagrams.
Pro — highest bandwidth; co-located teams move fastest with real sticky notes.
Con — no record unless you photograph it; needs everyone in a room.
Choose when the team is in one place — still the gold standard.
DDD's boundaries map cleanly onto system-level shapes — these decks own the mechanics.
A bounded context is the natural seam to split a microservice along — language and ownership that change together.
See Architecture Patterns for monolith → service trade-offs.
Domain events become integration events crossing contexts over a message broker.
See Message Queues & Streaming for delivery and ordering.
Tools like Axon (JVM) or an event-store database help with event sourcing & CQRS — but they are an implementation choice, never a prerequisite.
Choose only when event sourcing already earns its keep.
The most senior DDD skill isn't aggregates — it's knowing which parts of your system deserve the full treatment and which should stay a boring CRUD form. That call is strategic design, and sometimes the answer is "don't use DDD here at all."
The part that makes the business special — the pricing engine, the matching algorithm. Invest your best people and full DDD here.
Needed for the core to work but not a differentiator. Model it lightly — enough structure, no gold-plating.
Auth, notifications, billing rails. Buy or use off-the-shelf— don't lovingly hand-model what a SaaS already does.
Five quick questions on language, contexts, aggregates, events, and the strategic call — instant feedback, no sign-in.
Navigate with ← → or scroll · back to library