Domain-Driven Design: Strategic Design

DDD's big-picture tools. Domains and subdomains, ubiquitous language, bounded contexts, all the context-mapping patterns, and event storming to discover them.

The big idea

Software fails most often not because of bad code, but because developers misunderstand the business. Ask a warehouse worker and a salesperson what a "customer" is, and you'll get two different answers, both correct in their own world.

Domain-Driven Design (Eric Evans, 2003) says: put the business domain at the centre of software design, build a shared language with domain experts, and draw explicit boundaries where that language changes.

DDD has two halves:

Strategic design (this lesson)Tactical design (next lesson)
Zoom levelThe whole business and systemCode inside one boundary
ToolsSubdomains, ubiquitous language, bounded contexts, context mapsEntities, value objects, aggregates, repositories, domain events
AnswersWhere do we draw boundaries? What deserves the most care?How do we model the rules in code?

Strategic DDD: subdomains in the problem space, bounded contexts in the solution spaceStrategic DDD: subdomains in the problem space, bounded contexts in the solution space

1. Domain and subdomains (the problem space)

The domain is the business area the software serves: online retail, insurance, logistics. It splits into subdomains, and not all of them are equally important.

TypeWhat it isStrategyExample (online shop)
⭐ CoreWhat makes you different and wins customersBuild in-house, best people, richest modelPersonalised recommendations, dynamic pricing
🧰 SupportingNecessary and specific to you, but not a differentiatorBuild simply, or outsourceReturns handling, supplier onboarding
📦 GenericEvery business needs it, and it's solved alreadyBuy or use SaaS/open sourceAuth, payments, email, accounting
Drawing diagram…

💡 Spending your best engineers on building your own login system (generic) while the recommendation engine (core) is a mess is a classic strategic mistake.

2. Ubiquitous language

A ubiquitous language is a shared vocabulary that developers and domain experts use everywhere: in conversations, documents, tests and the code itself.

Drawing diagram…
// ❌ Technical language nobody in the business uses
if (rec.st === 3 && Date.now() - rec.ts > 2592000000) rec.st = 7;

// ✅ The business's own words
if (policy.isInGracePeriod(today) === false && policy.hasUnpaidPremium()) {
  policy.lapse();
}

Build a glossary and keep it next to the code:

TermMeaning in the Insurance context
PolicyA contract covering a customer against defined risks
PremiumThe amount the customer pays for a period of cover
Grace period30 days after a missed premium during which cover continues
LapseThe policy ends because the premium wasn't paid within the grace period

When the code uses a different word than the business, that's a smell. Rename the code.

3. Bounded contexts ⭐

A bounded context is an explicit boundary inside which one model and one language apply consistently. Outside it, the same word may mean something else, and that's OK.

Drawing diagram…

Trying to build one Customer class that serves all three creates a bloated model that every team fights over. Instead, each context has its own small model, sharing only an identity (the customer ID).

Subdomain vs bounded context: a subdomain is part of the business (problem space); a bounded context is part of the software (solution space). Ideally they line up one-to-one, but not always, especially in legacy systems.

Bounded contexts are the best starting point for module boundaries in a modular monolith, and for service boundaries in microservices. One team should own each context.

4. Context mapping: how contexts relate

A context map shows the bounded contexts and the relationships between them, which are as much about teams and politics as about code.

PatternRelationshipUse when
PartnershipTwo teams succeed or fail together; they plan jointlyTightly linked features, good collaboration
Shared kernelA small model shared and co-owned by two contextsTruly shared concepts; keep it tiny
Customer / SupplierUpstream (supplier) plans around downstream (customer) needsDownstream has influence over the upstream API
ConformistDownstream simply adopts the upstream model as it isUpstream won't change (a big vendor), and its model is acceptable
Anti-Corruption Layer (ACL)Downstream translates the upstream model into its ownUpstream model is messy or legacy; protect your model
Open Host Service (OHS)Upstream offers a well-defined public API for everyoneMany consumers
Published LanguageA documented shared format (schemas, events)Paired with OHS: e.g. JSON Schemas, Avro events
Separate WaysNo integration at all; duplicate if neededIntegration costs more than it's worth
Drawing diagram…

Anti-corruption layer in code

// Legacy ERP gives us this…
type ErpCustomerRecord = { CUST_NO: string; NM1: string; NM2: string; STAT_CD: "A" | "I" | "B" };

// …our Billing context wants this.
type BillingCustomer = { id: string; fullName: string; canBeInvoiced: boolean };

// The ACL is an Adapter that keeps legacy concepts OUT of our model
export class ErpCustomerTranslator {
  toBillingCustomer(record: ErpCustomerRecord): BillingCustomer {
    return {
      id: record.CUST_NO,
      fullName: `${record.NM1} ${record.NM2}`.trim(),
      canBeInvoiced: record.STAT_CD === "A",   // "B" = blocked, "I" = inactive
    };
  }
}

5. Event storming: discovering the contexts

Event storming (Alberto Brandolini) is a workshop: domain experts and developers put orange sticky notes on a long wall, one for every domain event (something that happened, in the past tense), in time order. Then add commands, actors, policies and hot spots.

Sticky noteColourExample
Domain event🟧 OrangeOrder Placed, Payment Failed
Command🟦 BluePlace Order
Actor🟨 Small yellowCustomer, Warehouse clerk
Policy ("whenever… then…")🟪 LilacWhenever payment fails, then notify the customer
External system🩷 PinkStripe
Hot spot / question🟥 RedWhat if stock runs out after payment?
Drawing diagram…

Where the language changes and events cluster together, you've found the edges of bounded contexts (here: Ordering, Billing, Fulfilment).

Strategic design → architecture

Drawing diagram…

Key takeaways

  • DDD puts the business domain at the centre, built together with domain experts.
  • Classify subdomains: invest in the core, keep supporting simple, buy generic.
  • Speak a ubiquitous language, in conversation and in code.
  • A bounded context is a boundary with one consistent model; the same word can mean different things in different contexts.
  • Context maps describe how contexts integrate: ACL, OHS, customer/supplier, conformist, shared kernel…
  • Event storming is a fast, visual way to discover the boundaries.