Concept · Architecture Patterns

Domain-Driven Design

01

Why this matters

"What does Order mean?" In your warehouse system, Order = a list of SKUs to pick. In your billing system, Order = an invoice with payment terms. In your shipping system, Order = a box with an address. Same word, three completely different concepts. Build all three under one Order class and you create the worst kind of coupling — every change ripples through systems that share nothing in common.

Domain-Driven Design (DDD) — Eric Evans, 2003 — is the discipline of letting language and concepts mirror the business. The big idea: identify bounded contexts where each domain word has one specific meaning, and let those contexts be the natural boundaries of your services.

02

Bounded contexts — the only DDD idea you need

A bounded context is a place where a particular model + ubiquitous language applies. Outside that boundary, the same word can mean something else.

E-commerce example. The word "Customer":

  • In Sales context: name, email, billing address, payment methods, lifetime value.
  • In Support context: name, contact channels, recent tickets, satisfaction score.
  • In Recommendations context: user_id, browsing history, embedding vector, segment.

Trying to build one "Customer" object that holds all this is the path to a 200-field nightmare. Three bounded contexts, three different Customer models. They share an ID; they don't share a class.

03

The DDD vocabulary

TermWhat it meansExample
Bounded ContextBoundary inside which one model is consistent"Sales", "Support", "Inventory"
Ubiquitous LanguageVocabulary shared between developers + business inside one context"Order" in Sales = list of cart items pre-checkout
AggregateCluster of objects that change together; treat as a unitOrder + LineItems together — never modify a LineItem without going through Order
Aggregate RootThe single entry point to an aggregateOrder is the root; access LineItems via Order, not directly
EntityObject with stable identity beyond its attributesCustomer (you can change their name; they're still the same customer)
Value ObjectDefined entirely by attributes; immutableMoney(100, USD), Address — equal if attributes equal
Domain EventSomething the business cares about that happenedOrderPlaced, PaymentReceived, ShipmentDispatched
Anti-Corruption LayerTranslation layer between two contexts so neither leaks into the otherAdapter that maps "Sales Customer" to "Support Customer"
04

Aggregates in practice

The most practically useful DDD idea after bounded contexts. An aggregate is a transactional consistency boundary. Rules:

  1. One aggregate per database transaction. Don't touch two aggregates atomically — if you need to, they should be one aggregate.
  2. External code only references aggregates by ID, never by direct object reference.
  3. Inside an aggregate, you can have rich object graphs. Outside, you can't reach in.
  4. Cross-aggregate consistency is eventual — via domain events.

Example: Order aggregate contains LineItems. Adding a line item: order.addItem(product, qty). The order itself enforces invariants ("max 100 items", "no duplicate SKUs"). You never call lineItem.setQuantity() directly.

Why this matters at scale: aggregates become natural sharding boundaries, natural transaction boundaries, natural cache keys. The boundary you draw in your code is the boundary you exploit in infrastructure.

05

Deep dive — context mapping

Once you have bounded contexts, you have to decide how they relate. Eric Evans defined seven patterns; the practically important ones:

Shared Kernel — two contexts share a small subset of model. Tight coupling. Use sparingly.

Customer/Supplier — context A's needs drive context B's interface. B has to support A's use cases. Negotiated.

Conformist — A uses B's interface as-is, no protection. Cheap; A is at B's mercy when B changes.

Anti-Corruption Layer (ACL) — A wraps B's interface in an adapter that translates to A's preferred terms. Insulates A from B's changes. The professional choice for any cross-team integration.

Open Host Service — B publishes a stable, well-documented API for many consumers. Higher cost for B; many As benefit.

Published Language — common interchange format like JSON Schema, OpenAPI, Avro. Decouples parties via the schema.

Interview answer

"We organize services around bounded contexts derived from business subdomains. Each context owns its data and exposes a context-specific interface. Cross-context integration goes through anti-corruption layers + domain events to prevent model leakage. Aggregates are the unit of transaction inside each context."

06

Real-world

Stripe APIs

Bounded contexts visible in API

Charges, Customers, Subscriptions, Connect — each a context with its own model. Same "Customer" appears differently across them.

Amazon's two-pizza teams

Service per bounded context

Inventory team owns the Inventory context. Recommendations team owns recs context. Each ~5-10 engineers.

Microsoft eShopOnContainers

DDD reference impl

Open-source .NET reference architecture explicitly built around DDD. Catalog, Basket, Ordering, Payment as separate bounded contexts.

Banking core systems

Aggregate-heavy

Account aggregate, Transaction aggregate, Loan aggregate. Each transaction touches one. Fits banking's invariants perfectly.

07

Used in problems

E-commerce explicitly applies bounded contexts (Catalog vs Cart vs Order vs Inventory vs Shipping). Payment gateway uses aggregates (Payment, Refund) for atomic state transitions. Uber's domain split (Rides, Eats, Freight) is bounded contexts at the org level.

Next up