Playbook

DDD in Production: Distributed Systems and Modernization Strategies (Ddd In Production Distributed Systems And Modernization)

How does DDD survive production change? A guide to Event Storming, Saga, Transactional Outbox, Anti-Corruption Layers, and Strangler Fig modernization.

Production DDD practices for event discovery, legacy modernization, and safe distributed change

The map for this chapter

This chapter is not a list of patterns to memorize. It follows how business truth inside a monolith can move safely while the system changes.

Monolith
   ↓
Event Storming
   ↓
Bounded Context
   ↓
Saga
   ↓
Outbox
   ↓
Strangler Fig
   ↓
Production

The real production question

Drawing DDD boundaries is the beginning. In production, the test is preserving those boundaries, the system, and user trust while the business changes. Rewriting an order flow from a legacy monolith overnight can be tempting, but it is usually the riskiest option.

Monolith → current business truth
New context → cleaner model
Incremental migration → measurable confidence

The goal is not to create more services. It is to let the system change safely when the language of the business changes.

Concepts at first use

📦 Event Storming
A discovery workshop that makes business events, commands, and risks visible together.

📦 Saga
A model that coordinates local transactions and business compensation after failure in a distributed workflow.

📦 Transactional Outbox
A pattern that records a data change and the event to publish in one local transaction.

📦 Strangler Fig
A modernization strategy that replaces a legacy system incrementally behind new boundaries rather than all at once.

A domain event is a business fact named in the past tense: OrderConfirmed. A command is intent: ConfirmOrder. The distinction matters because intent can be rejected, while a fact is a record other contexts can safely react to.

Use Event Storming to find the invisible flow first

A production migration starts with the business flow, not a code inventory. Product, operations, and engineering place past-tense events on the same wall, then add commands, actors, external systems, and red hot spots.

OrderConfirmed ← ConfirmOrder ← Customer
       ↓
InventoryReserved ← ReserveInventory
       ↓
PaymentCaptured ← CapturePayment ← Payment Gateway

Read the flow backwards too. Reverse narrative exposes decisions hidden by the happy path: returns, partial payment, timeouts, and manual intervention. A Bounded Context is found where language, decision, and ownership change—not where a table happens to end.

🎯 Goal of this section

Understand how business consistency survives when transaction boundaries are distributed.

Once money has left a bank, an SQL rollback cannot put it back. Once inventory is reserved in another context, a rollback in one database cannot undo it either. Distributed systems therefore compensate a business effect instead of pretending to roll it back.

Saga is not rollback; it is business compensation

Payment, inventory, and shipment live in different contexts, so they cannot share one ACID transaction. 2PC can promise strong consistency, but its lock retention and availability cost under coordinator or participant failure are poor production trade-offs. A saga defines compensation for each local step instead.

Reserve inventory → Capture payment → Create shipment
                 payment fails → Release inventory

ReleaseInventory must be idempotent too: a duplicate event must not increase stock twice. Choreography can be sufficient for short local flows. For long, business-critical workflows, orchestration provides a state machine and one observation point.

🎯 Goal of this section

Close the gap between a database record and publishing an event.

An order is saved successfully. At that exact moment the broker goes down. The order exists but OrderConfirmed does not; shipping and notification silently fall behind. Outbox closes the gap by storing the record and its publishable event in one local transaction.

Outbox and observability make migration provable

If an order commits while the broker is unavailable, a gap appears between the database write and message publication. That is why Outbox exists.

Local transaction
  → save Order
  → write OrderConfirmed to Outbox
  → commit

Relay → Broker → Consumer → Projection

Consumers must expect duplicates under at-least-once delivery. Check event identity, aggregate version, and business effect. A correlation ID connects one end-to-end flow; a causation ID names the decision that produced an event. They are not just log fields: during an incident they answer “what happened?” and “why?”.

🎯 Goal of this section

Move to a new domain boundary without losing legacy behaviour.

You cannot rewrite an ERP that has run for ten years over one weekend. First measure where the new model differs from live legacy behaviour; then route traffic in small, reversible increments.

Modernize legacy with Strangler Fig

Big-bang migration risks losing old behaviour before its rules are discovered. Place the new context next to the legacy system; measure data and behaviour differences first, then route traffic.

1. Replication only  → feed data into the new model
2. Shadow reads      → compare legacy and new responses
3. Partial reads     → route a small traffic percentage
4. Write migration   → write at the new boundary, preserve compatibility
5. Full cutover      → switch after metrics validate it
6. Decommission      → remove bridges and old code

The Anti-Corruption Layer protects the new model. Instead of carrying a legacy's ambiguous fields into the domain, it translates them: LEGACY_ORDER_STATE=7 becomes a meaningful FulfilmentStatus. The dirty model's blast radius remains in one adapter.

The acceptance criterion: more than a successful deploy

A migration does not succeed merely because a new endpoint responds. Shadow-read difference rate, P95 latency delta, consumer lag, DLQ depth, and a rollback plan must be visible. Replay cost is O(n) in the event count; snapshots and segmented replay can reduce it, but they do not replace verified history.

An unobservable system cannot be operated. Every event contract needs an owner, versioning strategy, alert, and replay runbook.

Associations that create false confidence

❌ DDD = microservice migration
✓ DDD protects business language and decision boundaries; microservices are sometimes an outcome.

❌ Saga = technical rollback
✓ A saga compensates an irreversible business effect.

❌ Outbox = exactly-once delivery
✓ Outbox prevents lost events; consumers still need idempotency for duplicates.

❌ Shadow read = testing complete
✓ It is ongoing evidence about behavioural differences in live traffic.

❌ ACL = unnecessary layer
✓ ACL keeps legacy language from contaminating the new domain.

Production-readiness checklist

  1. Does Event Storming expose non-happy-path hot spots and external systems?
  2. Does every Saga step have an idempotent compensation?
  3. Does Outbox close the gap between the database record and the event when the broker fails?
  4. Are correlation ID, causation ID, consumer lag, and DLQ observable?
  5. Does the new context protect itself from the legacy model with an ACL?
  6. Are thresholds defined for shadow reads, gradual traffic, and rollback?
  7. Is the owner and removal date of every transitional bridge explicit?

Migration complexity is not measured by service count. Measure independent change, fault isolation, and evidence that you can return safely.

What should remain with you

  1. Production DDD means the model remains faithful to business language under change.
  2. Event Storming discovers invisible decisions and risks before code.
  3. Saga, Outbox, and idempotency treat distributed failure as a design input.
  4. Strangler Fig and ACL divide legacy modernization into measurable, reversible steps.

Modernization is not destroying the old system. It is creating a safer rhythm of change without losing business value.

FAQ

Frequently asked questions

What is Event Storming?

A discovery workshop that makes business events, commands, and risks visible together.

What is Saga?

A model that coordinates local transactions and business compensation after failure in a distributed workflow.

Is it true that "DDD = microservice migration"?

DDD protects business language and decision boundaries; microservices are sometimes an outcome.

What does this part lock in?

The goal is not to create more services. It is to let the system change safely when the language of the business changes. Production DDD means the model remains faithful to business language under change. Drawing DDD boundaries is the beginning. In production, the test is preserving those boundaries, the system, and user trust while the business changes. Rewriting an order flow from a legacy monolith overnight can be tempting, but it is usually the riskiest option.

Engineering Principles Learned

  • Production DDD requires events and boundaries to retain business meaning under failure.
  • Saga, Outbox, and idempotency accept distributed delivery as normal rather than exceptional.
  • Strangler Fig and ACL keep legacy modernization small, measurable, and reversible.

Continue reading

Continue reading

Related articles

ESSAY

How does DDD scale in large systems? A decision guide to Bounded Contexts, Context Mapping, Conway's Law, modular monoliths, microservice boundaries, and…

Related articles

Related articles

Paylaş