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…

Bounded contexts and context mapping for a large domain-driven system

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:

  1. Does the same word mean different things in different meetings?
  2. Are special cases such as if (shipping) and if (sales) multiplying?
  3. Do teams repeatedly wait for each other to make one change?
  4. 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

  1. Where does the same word start to mean different things?
  2. Does every context have a clear language, owner, and success measure?
  3. Can the boundary be validated in a modular monolith first?
  4. Is an upstream model leaking directly into your domain, requiring an ACL? In ERP → ACL → Shipping, for example, CUST_TIER=7 should become Shipping's DeliveryEligibility.
  5. Is the shared contract versioned and backward compatible?
  6. 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.
  7. 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

  1. Large systems have no single true model; every Bounded Context carries its own business reality.
  2. A microservice is not the start of a strategic boundary; it can be its physical outcome.
  3. Context Mapping makes contract, power, and model-protection decisions visible.
  4. 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

Related articles

Related articles

Paylaş