Playbook
How Does DDD Work in Large Systems? (How Does Ddd Work In Large Systems)
How does DDD scale in large systems? A decision guide to Bounded Contexts, Context Mapping, Conway's Law, modular monoliths, microservice boundaries, and…
Start by dropping the wrong question
On day one there was one Book model. Then Sales needed a price, Shipping needed a delivery window, and Marketing added campaign data. Six months later, the same class had become an object nobody fully understood. The problem was not that the book grew; it was that different business intentions were loaded onto one model.
Splitting a large system into microservices is not the same as applying DDD. First understand which parts of the business have different language, decisions, and rates of change. Otherwise, a monolith becomes a more expensive distributed monolith.
One model → tries to carry every need → model tension
Sales context → price and customer credit
Shipping context → delivery address and parcel
Catalog context → title, ISBN, and metadata
This chapter asks: how do we let models in the same business converse without contaminating each other?
Concepts at first use
📦 Bounded Context
An explicit boundary in which a model, language, and rules remain consistent.
📦 Context Mapping
The practice of deliberately defining the data, power, and contract relationship between two contexts.
📦 Upstream / Downstream
The provider of a model or contract is upstream; its dependent consumer is downstream.
📦 Anti-Corruption Layer (ACL)
A translation layer that keeps an external model from leaking into your domain.
Conway's Law in plain language: software eventually reflects the way the organization communicates. A Bounded Context is not a microservice; it can first exist as a separate language, ownership, and code boundary inside a modular monolith.
The big picture
Company
│
┌────────────────────┼────────────────────┐
│ │ │
Catalog Sales Shipping
│ │ │
Book Customer Recipient
│ │ │
└──────── OrderConfirmed event ────────┘
│
Outbox
│
Broker
│
Delivery projection
The diagram does not describe a required number of physical services. It shows where language, ownership, and change live.
Why does the search for one true model fail?
In a catalog, a book is title, author, and ISBN. In lending, it is a physical copy on a shelf. In sales, it is SKU and price. Combining all of these into one Book class is not reuse; it binds different intentions together. The same tension appears with Customer: sales needs credit and billing details while shipping needs an address and delivery preference.
❌ Unified Customer
creditLimit + invoiceAddress + deliveryWindow + marketingConsent + ...
✓ Sales.Customer
creditLimit + billingProfile
✓ Shipping.Recipient
deliveryAddress + deliveryWindow
The issue is not object count. It is a single model changed by different teams for different reasons. As the number of affected contexts k grows, change cost is at least O(k); tests and coordination grow faster when dependencies multiply.
Bounded Context: draw the boundary in business language first
Do not follow technical layers or database tables. Look for these signals:
- Does the same word mean different things in different meetings?
- Are special cases such as
if (shipping)andif (sales)multiplying? - Do teams repeatedly wait for each other to make one change?
- Is the owner, success metric, or change rhythm of a field unclear?
Conway's Law is a warning here: system boundaries often mirror organizational communication. If a team must make independent decisions, its model and deployment boundary should be independent as far as practical. This is an ownership decision, not a technical goal.
Start in a monolith; treat microservices as an outcome
A modular monolith is the lowest-risk way to validate a Bounded Context. Each context can own its application/domain/infrastructure components, data access, and explicit contract without yet paying the operational cost of distributed calls.
Catalog module ── published contract ──► Sales module
Sales module ── domain event ─────────► Shipping module
Move to a microservice only when independent scaling, deploys, security boundaries, or real team autonomy are proven needs. Bounded Context = Microservice is the common equation behind premature distribution.
Context Mapping: integration should not be accidental
The relationship between contexts is determined by power balance and model contamination risk, not by an endpoint.
| Situation | Strategy | Why |
|---|---|---|
| Legacy model is chaotic | ACL | Prevents external language leaking into the domain |
| Many consumers | Open Host Service + Published Language | Shares a versioned contract, not internal model |
| No influence over a clean upstream | Conformist | Avoids needless translation |
| Small, stable shared piece | Shared Kernel, last resort | Accepts coordination cost deliberately |
An ACL can translate a legacy ERP's CUST_TIER=7 into DeliveryEligibility inside Shipping. The ERP term never enters the shipping domain. The extra code cost keeps the blast radius at one translation point.
Events, Outbox, and eventual consistency
One context should not read another context's database. When Sales confirms an order it may publish OrderConfirmed; Shipping builds its delivery view from it. Events do not ban synchronous calls, but reduce temporal coupling for independent workflows.
Sales transaction
→ save Order
→ write OrderConfirmed to Outbox
→ commit
Relay → Broker → Shipping consumer → Delivery projection
Outbox is necessary because the broker and database do not share one ACID transaction. If the order exists, an event exists to publish; the relay can retry. Consumers must still be idempotent because duplicates are normal. Eventual consistency is not a bug: it is a visible time window the product must accept.
The cost of this design
Context boundaries require more contracts, observability, versioning, and team discipline. Every consumer adds more than O(1) code: dashboards, alerts, retries, ownership, and integration tests follow. The best boundary is not the one creating the most services; it is the one that actually lowers the cost of change.
Associations that create false confidence
❌ Bounded Context = microservice
✓ It is first a language, ownership, and model boundary.
❌ One model = consistency
✓ Each context can hold its own consistent model.
❌ Shared Kernel = reuse
✓ It is shared change and coordination cost.
❌ A REST call = integration strategy
✓ Contract, ownership, and failure behaviour form the strategy.
❌ Eventual consistency = error
✓ Properly designed, it is a conscious trade-off for independent workflows.
Strategic design checklist
- Where does the same word start to mean different things?
- Does every context have a clear language, owner, and success measure?
- Can the boundary be validated in a modular monolith first?
- Is an upstream model leaking directly into your domain, requiring an ACL? In
ERP → ACL → Shipping, for example,CUST_TIER=7should become Shipping'sDeliveryEligibility. - Is the shared contract versioned and backward compatible?
- What happens when an order commits and the broker is down? Outbox writes the record and the event to one local transaction; a relay retries safely when the broker returns.
- Are product and operational decisions in place for lag, duplicates, and replay?
The code does not merely gain a few lines: monitoring, alerts, retries, and operational ownership arrive too. Code growth may look like O(1), but operational cost does not grow linearly. Measure independent change and fault isolation, not service count.
What should remain with you
- Large systems have no single true model; every Bounded Context carries its own business reality.
- A microservice is not the start of a strategic boundary; it can be its physical outcome.
- Context Mapping makes contract, power, and model-protection decisions visible.
- Events and Outbox carry change safely between independent contexts.
An architectural boundary begins not where code lives, but where a decision is made in one language under one owner.
The final chapter examines DDD in production: Event Storming, legacy migration, Anti-Corruption Layers, and carrying change safely.
FAQ
Frequently asked questions
What is Bounded Context?
An explicit boundary in which a model, language, and rules remain consistent.
What is Context Mapping?
The practice of deliberately defining the data, power, and contract relationship between two contexts.
Is it true that "Bounded Context = microservice"?
It is first a language, ownership, and model boundary.
What does this part lock in?
This chapter asks: how do we let models in the same business converse without contaminating each other? Large systems have no single true model; every Bounded Context carries its own business reality. On day one there was one `Book` model. Then Sales needed a price, Shipping needed a delivery window, and Marketing added campaign data. Six months later, the same class had become an object nobody fully understood. The problem was not that the book grew; it was that different business intentions were loaded onto one model.
Engineering Principles Learned
- A Bounded Context is first a business-language and ownership boundary, not necessarily a microservice.
- Context Mapping deliberately protects the domain from external models.
- Events and Outbox carry change safely and observably between independent contexts.
Continue reading
Continue reading
Related articles
DDD in Production: Distributed Systems and Modernization Strategies
How does DDD survive production change? A guide to Event Storming, Saga, Transactional Outbox, Anti-Corruption Layers, and Strangler Fig modernization.
Related articles
How Does a CQRS (Command Query Responsibility Segregation) Pipeline Work? Anatomy of Command and Query Flows
What is a CQRS request pipeline? How does an HTTP request travel through Controller, MediatR, pipeline behaviors, a handler, Outbox, and a read model?
Related articles
How Does a Vertical Slice Work from the Inside?
A technical guide to the inner flow of a Vertical Slice: request, validation, handler, aggregate, outbox, projection, idempotency, performance, tests, and…