← Back to all projects

DDD TYPESCRIPT LEARNING PROJECTS

Most software doesn’t fail because the team can’t “code” — it fails because the *business rules* become unmanageable:

Learn Domain-Driven Design (DDD) in TypeScript: From Messy Rules to Reliable Domain Code

Goal: Learn to turn messy, ever-changing business rules into clean, reliable TypeScript software using domain-driven design. You’ll practice extracting a ubiquitous language with domain experts, modeling entities/value objects/aggregates that reflect real-world constraints, splitting systems into bounded contexts that can evolve independently, and enforcing invariants so your system stays consistent as requirements change. By the end, you’ll be able to design domain models that survive change—not by being “generic”, but by being true to the domain.


Why Domain-Driven Design Matters

Most software doesn’t fail because the team can’t “code” — it fails because the business rules become unmanageable:

  • Rules live in scattered if statements and database triggers.
  • Different teams use the same words to mean different things (“customer”, “account”, “order”).
  • New features accidentally break old behavior (“we didn’t realize refunds had 7 states…”).
  • Integrations force weird compromises (“the vendor API calls it ‘status’, but it’s not the same as ours”).

DDD is a response to that reality: it gives you a way to model meaning, control change, and keep invariants true even as the system evolves.

The DDD Problem, Visualized

                 "Business rules"
                        |
                        v
      ┌─────────────────────────────────────┐
      │  Messy implementation ("rules soup")│
      │                                     │
      │  controllers -> services -> helpers │
      │      |          |         |         │
      │   if/else     if/else    if/else    │
      │      |          |         |         │
      │   DB triggers  cron jobs  queues    │
      └─────────────────────────────────────┘
                        |
                        v
            Bugs, fear of change, slow delivery

DDD aims for this:

┌───────────────────────┐
│  Ubiquitous Language  │  (shared meaning)
└───────────┬───────────┘
            |
            v
┌───────────────────────┐
│     Domain Model       │  (entities, value objects,
│  + Invariants/Policies │   aggregates, events)
└───────────┬───────────┘
            |
            v
┌───────────────────────┐
│  Application Layer     │  (use-cases: commands/queries)
└───────────┬───────────┘
            |
            v
┌───────────────────────┐
│ Infrastructure         │  (DB, APIs, queues, UI)
└───────────────────────┘

Core Concepts Deep Dive (DDD + TypeScript)

1) Ubiquitous Language: The Real “Framework” of DDD

DDD starts with language. If your team can’t agree on words, your code can’t agree on meaning.

Domain Expert says: "An order is confirmed when payment is captured."
Engineer hears:     "confirmed = status=CONFIRMED"
Support says:       "confirmed = customer got email"

DDD move: define the term precisely, with examples and boundaries.

TypeScript tie-in: use types and module boundaries to encode language—but only after the language is shared and stable enough to encode.


2) Entity vs Value Object (and Why This Matters for Bugs)

ENTITY (identity over time)          VALUE OBJECT (meaning by attributes)
┌──────────────────────────┐        ┌──────────────────────────┐
│ Customer                 │        │ Money                    │
│ id = cust_123            │        │ amount = 10.00           │
│ name can change          │        │ currency = USD           │
│ "same person" persists   │        │ immutable, replaceable   │
└──────────────────────────┘        └──────────────────────────┘
  • Entities change over time but remain “the same thing”.
  • Value objects are compared by value and should be immutable.

TypeScript tie-in:

  • Branded/opaque identifiers prevent mixing IDs (e.g., CustomerId vs OrderId).
  • Immutable value objects prevent “spooky action at a distance”.

3) Aggregates: Your Consistency Boundary

An aggregate is a cluster of objects that must remain consistent together.

      Aggregate Boundary (consistency)
┌─────────────────────────────────────┐
│ Order (Aggregate Root)              │
│  - lineItems[]                      │
│  - shippingAddress                  │
│  - status                           │
│                                     │
│ Invariants enforced HERE:           │
│  - can't ship if not paid           │
│  - can't pay twice                  │
│  - totals must match                │
└─────────────────────────────────────┘

Outside: Payment, Shipping, Inventory ... may be separate aggregates/contexts.

Rule of thumb: If a rule must be true immediately, it must be enforced within the aggregate boundary.


4) Invariants, Validations, and “Where Rules Live”

DDD distinguishes:

  • Invariant: must always be true (e.g., “quantity can’t be negative”).
  • Policy: a business decision that can change (e.g., “VIP customers can exceed credit limit by 10%”).
  • Workflow: a multi-step process across aggregates/contexts (e.g., fulfillment).
Command -> Application Service -> Aggregate -> Domain Events
           (use-case)             (enforce)   (tell what happened)

TypeScript tie-in:

  • Use runtime validation at boundaries (HTTP/queue) and domain-level validation for invariants.
  • Keep “validation libraries” from leaking into domain semantics; they’re implementation details.

5) Bounded Contexts: Same Word, Different Meaning

“Customer” in Sales is not necessarily “Customer” in Support.

┌───────────────┐       Translation        ┌───────────────┐
│ Sales Context  │ <--------------------->  │ Support Context│
│ "Customer"     │                         │ "Customer"     │
│ = buyer        │                         │ = ticket owner  │
└───────────────┘                         └───────────────┘

Bounded contexts allow:

  • Independent models
  • Independent release cycles
  • Explicit translation (so meaning doesn’t leak)

6) Context Mapping and Anti-Corruption Layers (ACL)

When integrating, you either:

  • Let external models leak into yours (you pay later), or
  • Translate at the boundary (you pay now, but less overall).
Vendor API DTOs
   |
   v
┌──────────────┐
│   ACL Layer  │  maps vendor terms -> your domain concepts
└──────┬───────┘
       v
  Your Domain Model

7) Domain Events: “What Happened” (Not “What To Do”)

Domain events help you build systems that evolve:

OrderPlaced  ->  (Shipping prepares)  ->  ShipmentCreated
             ->  (Email sends)        ->  CustomerNotified
             ->  (Accounting records) ->  InvoiceIssued

Key point: events represent facts in the past; handlers represent reactions that can change.


8) Tactical vs Strategic DDD (You Need Both)

  • Tactical DDD: entities/value objects/aggregates/events/repositories.
  • Strategic DDD: bounded contexts, context maps, team boundaries, integration patterns.

You’ll practice both: tactical modeling without strategic boundaries tends to become a “big ball of mud” with fancy classes.


Concept Summary Table

Concept Cluster What You Need to Internalize
Ubiquitous language Shared words are shared meaning; without it, code is “correct” but wrong.
Entity vs value object Identity and mutability are design decisions that control bugs and change.
Aggregates Consistency has a boundary; invariants must be enforced inside it.
Invariants vs policies “Must always be true” differs from “currently true for business reasons”.
Bounded contexts Same term can mean different things; split and translate intentionally.
Context mapping / ACL Integrations are translation problems; don’t let foreign models infect your domain.
Domain events Model facts; enable evolution; decouple reactions from core meaning.
Application vs domain Use-cases coordinate; domain enforces; infrastructure persists/integrates.

Deep Dive Reading by Concept

This maps core concepts to dependable references. Use it alongside the projects.

Foundations: Language, Model, and Implementation

Concept Book & Chapter
Ubiquitous language Domain-Driven Design by Eric Evans — Ch. 2: “Communication and the Use of Language”
Model + implementation Domain-Driven Design by Eric Evans — Ch. 3: “Binding Model and Implementation”
Isolating the domain Domain-Driven Design by Eric Evans — Ch. 4: “Isolating the Domain”

Entities, Value Objects, Aggregates

Concept Book & Chapter
Making concepts explicit Domain-Driven Design by Eric Evans — Ch. 9: “Making Implicit Concepts Explicit”
Object lifecycle (incl. aggregates) Domain-Driven Design by Eric Evans — Ch. 6: “The Life Cycle of a Domain Object”
Strategic modeling patterns (practical) Implementing Domain-Driven Design by Vaughn Vernon — Read Parts on tactical patterns and aggregates (use ToC as guide)

Boundaries and Large-Scale Design

Concept Book & Chapter
Maintaining model integrity (context boundaries) Domain-Driven Design by Eric Evans — Ch. 14: “Maintaining Model Integrity”
Large-scale structure Domain-Driven Design by Eric Evans — Ch. 16: “Large-Scale Structure”
Context mapping workshop mindset Implementing Domain-Driven Design by Vaughn Vernon — Read Parts on strategic design and context mapping (use ToC as guide)

Refactoring Toward Better Models

Concept Book & Chapter
Refactoring toward deeper insight Domain-Driven Design by Eric Evans — Ch. 13: “Refactoring Toward Deeper Insight”
Supple design Domain-Driven Design by Eric Evans — Ch. 10: “Supple Design”

Essential Reading Order (Suggested)

  1. Week 1 (Language + Model Basics): Evans Ch. 2, 3, 4
  2. Week 2 (Tactical Patterns): Evans Ch. 6, 9, 10 + Vernon (tactical sections)
  3. Week 3 (Boundaries + Integration): Evans Ch. 14, 16 + Vernon (strategic sections)
  4. Week 4 (Refactoring): Evans Ch. 13 (apply to Project 9)

Project 1: Ubiquitous Language Workshop Kit (Glossary + Scenarios + Context Map)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Requirements / Modeling
  • Software or Tool: Node.js CLI + Markdown generation
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A CLI that helps you run a DDD “discovery” workshop by collecting terms, rules, examples, commands, and events, then generating a glossary and a bounded-context diagram from that data.

Why it teaches DDD: DDD begins before code—this project makes you practice the hardest skill: turning fuzzy language into explicit concepts. You will learn how ambiguity becomes bugs, and how a glossary becomes architecture.

Core challenges you’ll face:

  • Converting informal statements into precise term definitions (maps to ubiquitous language)
  • Capturing example scenarios that reveal hidden rules (maps to making implicit concepts explicit)
  • Drawing boundaries when the same word means different things (maps to bounded contexts)

Key Concepts:

  • Ubiquitous Language: “Domain-Driven Design” by Eric Evans — Ch. 2
  • Binding Model and Implementation: “Domain-Driven Design” by Eric Evans — Ch. 3
  • Maintaining Model Integrity: “Domain-Driven Design” by Eric Evans — Ch. 14

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic TypeScript; basic CLI usage

Learning milestones:

  1. You can take a messy “business rule paragraph” and turn it into a glossary entry with examples
  2. You can identify at least 2 plausible bounded contexts from the same narrative
  3. You can explain which rules belong together (and which don’t) before you model aggregates

Real World Outcome

You’ll have a CLI that produces workshop artifacts your future-self (and teammates) can actually use:

  1. A GLOSSARY.md with terms, definitions, and examples
  2. A RULES.md list with numbered rules and scenarios
  3. A CONTEXT_MAP.md with a simple diagram of bounded contexts and translations

Example Output:

$ ddd-workshop import notes.txt
✓ Parsed 14 candidate terms
✓ Extracted 27 rule statements
✓ Found 9 example scenarios

$ ddd-workshop glossary
✓ Wrote ./GLOSSARY.md (14 terms)
✓ Wrote ./RULES.md (27 rules)

$ ddd-workshop context-map
✓ Suggested contexts: Sales, Billing, Fulfillment
✓ Wrote ./CONTEXT_MAP.md

$ ddd-workshop diff --from last-week.json --to today.json
Δ "Order" definition changed (Sales)
Δ New rule: "Refunds require approval over $200"
Δ "PaymentCaptured" event renamed to "FundsCaptured"

The Core Question You’re Answering

“Do we agree on what our words mean, and can we prove it with examples?”

If you can’t answer this, your code will drift into contradictions—because each feature will secretly implement a different interpretation of the same terms.

Concepts You Must Understand First

Stop and research these before coding:

  1. Ubiquitous Language
    • What’s the difference between a term definition and a rule?
    • How do examples expose ambiguity?
    • How do you keep language consistent across code and conversations?
    • Book Reference: “Domain-Driven Design” — Ch. 2 (Evans)
  2. Bounded Context
    • What does it mean for the “same word” to have different meanings?
    • What evidence suggests you need separate models?
    • Book Reference: “Domain-Driven Design” — Ch. 14 (Evans)

Questions to Guide Your Design

  1. Inputs
    • Where does workshop data come from: prompts, JSON, YAML, plain text?
    • How will you preserve examples verbatim (so you don’t “sanitize away” meaning)?
  2. Outputs
    • What’s the minimum useful artifact: glossary, rule index, context map?
    • How will you show diffs over time so language evolution is visible?

Thinking Exercise

Take this vague statement and rewrite it as (a) terms and (b) rules:

“Customers can cancel an order unless it’s already being prepared, but if they’re VIP we try to stop it anyway.”

Questions:

  • What is “being prepared” (a state, a workflow step, a timestamp, an external system)?
  • What does “try to stop it” mean—best effort, guarantee, or request?
  • What makes someone “VIP” and where does that information live?

The Interview Questions They’ll Ask

  1. “What problem does DDD solve that clean architecture alone doesn’t?”
  2. “What is a ubiquitous language and how do you enforce it?”
  3. “When should you split into bounded contexts?”
  4. “How do you prevent external API terms from leaking into your domain?”
  5. “What’s the biggest risk of ‘modeling too early’?”

Hints in Layers

Hint 1: Starting Point Define 1 domain narrative (1 page) and extract terms + examples manually first; only then automate.

Hint 2: Next Level Store workshop items in a simple structured format: term, definition, examples, rules, commands, events.

Hint 3: Technical Details Generate deterministic markdown so diffs are meaningful (stable ordering, consistent formatting).

Hint 4: Tools/Debugging Use snapshot tests on the generated markdown to ensure changes are deliberate and reviewable.

Books That Will Help

Topic Book Chapter
Language as a design tool “Domain-Driven Design” by Eric Evans Ch. 2
Defining boundaries “Domain-Driven Design” by Eric Evans Ch. 14

Project 2: TypeScript Value Object Toolkit (Validation + Normalization + Equality)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Rust
  • Coolness Level: Level 3: Actually Impressive
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Domain Modeling / Type Design
  • Software or Tool: TypeScript + test runner (Vitest/Jest)
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A small library of value objects (e.g., Money, EmailAddress, DateRange, Percent, Quantity) with strict creation rules, normalization, and consistent equality.

Why it teaches DDD: Value objects are where subtle bugs breed: currency mismatches, timezone errors, rounding issues, invalid emails, negative quantities. This project teaches you to push correctness into the types and constructors so the rest of the domain stays clean.

Core challenges you’ll face:

  • Defining “valid creation” vs “invalid input” as explicit outcomes (maps to invariants)
  • Normalizing values (e.g., case-fold emails, decimal precision) without losing meaning (maps to value objects)
  • Ensuring equality semantics are consistent everywhere (maps to model expressiveness)

Key Concepts:

  • Making Implicit Concepts Explicit: “Domain-Driven Design” by Eric Evans — Ch. 9
  • Supple Design: “Domain-Driven Design” by Eric Evans — Ch. 10
  • Life Cycle of Domain Objects: “Domain-Driven Design” by Eric Evans — Ch. 6

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Comfortable with TypeScript types; basic testing

Learning milestones:

  1. You can express “invalid input” without throwing everywhere
  2. You can explain how normalization affects equality and persistence
  3. You stop leaking primitive strings/numbers into your domain model

Real World Outcome

You’ll have a tiny npm-style library (even if only locally) that your other projects can reuse. You’ll be able to run tests and see:

Example Output:

$ pnpm test
✓ Money rejects negative amounts unless explicitly allowed
✓ Money refuses cross-currency addition (USD + EUR)
✓ EmailAddress normalizes case and whitespace
✓ DateRange ensures start <= end (and exposes duration)
✓ Percent clamps/validates 0..100 based on domain rules
✓ Quantity enforces integer vs decimal based on unit

Test Files: 8 passed
Tests:      74 passed
Time:       0.91s

The Core Question You’re Answering

“Which domain rules should be impossible to violate anywhere in the codebase?”

If rules can be violated “by accident”, they will be—especially during refactors and integrations.

Concepts You Must Understand First

  1. Value Objects
    • What makes something a value object (vs entity)?
    • What does immutability buy you in refactoring safety?
    • Book Reference: “Domain-Driven Design” — Ch. 9 (Evans)
  2. Invariants
    • What’s the difference between input validation and domain invariants?
    • Which invariants are global, and which are context-specific?
    • Book Reference: “Domain-Driven Design” — Ch. 10 (Evans)

Questions to Guide Your Design

  1. Creation
    • Do you allow “unsafe” construction for persistence rehydration, or must everything validate?
    • How will you communicate failure: Result, error objects, exceptions?
  2. Serialization
    • How will value objects persist to JSON/DB without leaking primitives everywhere?
    • What is the canonical string/number representation for each value object?

Thinking Exercise

Pick one value object (Money) and list 12 ways it can be wrong in a real system:

  • precision/rounding
  • currency mismatches
  • mixing gross/net amounts
  • negative values for refunds vs charges
  • taxation and minor units

Then decide: which are invariants, which are policies, and which are “application concerns”?

The Interview Questions They’ll Ask

  1. “What is a value object and why prefer it over primitives?”
  2. “How do you represent IDs safely in TypeScript?”
  3. “Where do you put validation logic: constructors, services, or controllers?”
  4. “How do you serialize and rehydrate value objects safely?”
  5. “How do you handle currency precision and rounding?”

Hints in Layers

Hint 1: Starting Point Start with just Money and make it painful to misuse.

Hint 2: Next Level Define a consistent pattern: create(input) -> success/failure, plus equals, plus toJSON.

Hint 3: Technical Details Treat “rehydration” as a separate concern; don’t weaken your rules just to satisfy persistence.

Hint 4: Tools/Debugging Add “abuse tests”: tests that attempt illegal operations and assert they fail deterministically.

Books That Will Help

Topic Book Chapter
Making concepts explicit “Domain-Driven Design” by Eric Evans Ch. 9
Designing for change “Domain-Driven Design” by Eric Evans Ch. 10

Project 3: Aggregate Modeling — Ordering + Payments (Invariants First)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 3: Actually Impressive
  • Business Potential: 3. The “SaaS-in-Waiting”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Domain Modeling / Consistency
  • Software or Tool: Node.js + tests + SQLite (optional)
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A small “Ordering” domain where an Order aggregate enforces rules around items, totals, payment capture, and state transitions.

Why it teaches DDD: Aggregates are where DDD becomes real: you must decide what must be consistent, what can be eventual, and how to prevent illegal transitions—without scattering rules across layers.

Core challenges you’ll face:

  • Choosing the aggregate boundary (maps to aggregates)
  • Encoding state transitions as explicit domain logic (maps to invariants)
  • Separating policies from invariants (maps to policy vs invariant)

Key Concepts:

  • Life Cycle of a Domain Object: “Domain-Driven Design” by Eric Evans — Ch. 6
  • Supple Design: “Domain-Driven Design” by Eric Evans — Ch. 10

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2 helpful; basic testing

Learning milestones:

  1. You can describe the aggregate’s invariants in plain English, then enforce them in one place
  2. You can reject illegal commands with clear domain reasons
  3. You can explain what belongs inside the aggregate and what must be handled outside

Real World Outcome

You’ll run a suite of scenario tests that read like business narratives. You’ll also be able to run a CLI “simulator” that executes commands and prints state transitions.

Example Output:

$ order-sim run scenarios/order-happy-path.json
✓ CreateOrder
✓ AddItem (SKU=CHAIR, qty=2)
✓ AddItem (SKU=DESK, qty=1)
✓ ConfirmOrder
✓ CapturePayment (amount=499.97 USD)
✓ MarkAsReadyToShip

Final State: READY_TO_SHIP
Total: 499.97 USD
Events Emitted:
 - OrderConfirmed
 - PaymentCaptured
 - OrderReadyToShip

Illegal transitions become explicit:

$ order-sim command CapturePayment --order ord_123 --amount 10.00
✗ Rejected by domain invariant
Reason: Cannot capture payment before order is confirmed

The Core Question You’re Answering

“What must be true immediately after a command completes—and who is responsible for enforcing it?”

DDD is largely a discipline of choosing where truth is enforced.

Concepts You Must Understand First

  1. Aggregate
    • What is an aggregate root?
    • Why can’t you just enforce rules “in the database” or “in the service layer”?
    • Book Reference: “Domain-Driven Design” — Ch. 6 (Evans)
  2. Invariants
    • Which rules are safety rules (must never be violated)?
    • Which rules are policies (can change) and how do you isolate them?
    • Book Reference: “Domain-Driven Design” — Ch. 10 (Evans)

Questions to Guide Your Design

  1. Boundary
    • Are discounts part of the Order aggregate, or a separate context?
    • Is payment capture part of the same aggregate, or does it become eventual via events?
  2. State
    • What are the valid states and transitions?
    • Which transitions require external confirmation (e.g., payment gateway)?

Thinking Exercise

Create a state machine for your order lifecycle:

DRAFT -> CONFIRMED -> PAID -> READY_TO_SHIP -> SHIPPED
   \-> CANCELED

Then answer:

  • Which transitions are reversible?
  • Which transitions are caused by external systems?
  • Which transitions must emit domain events?

The Interview Questions They’ll Ask

  1. “What is an aggregate and why do we need it?”
  2. “How do you decide an aggregate boundary?”
  3. “What’s the difference between a domain service and an application service?”
  4. “Where do you put state transition rules?”
  5. “How do you model invariants in a statically typed language?”

Hints in Layers

Hint 1: Starting Point Write invariants as bullet points before writing any model code.

Hint 2: Next Level Model commands and reject invalid ones with explicit domain reasons.

Hint 3: Technical Details Keep persistence out of the domain; use a repository interface and an in-memory implementation first.

Hint 4: Tools/Debugging Use scenario tests that mirror business narratives; treat them as living documentation.

Books That Will Help

Topic Book Chapter
Object lifecycle + invariants “Domain-Driven Design” by Eric Evans Ch. 6
Supple design in models “Domain-Driven Design” by Eric Evans Ch. 10

Project 4: Bounded Context Split — Catalog vs Ordering (Same Terms, Different Meaning)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 4: Portfolio-Worthy
  • Business Potential: 3. The “SaaS-in-Waiting”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Architecture / Context Boundaries
  • Software or Tool: Monorepo packages (optional), module boundaries, tests
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: Two bounded contexts with distinct models:

  • Catalog: products, SKUs, availability, descriptions
  • Ordering: order lines, prices-at-time-of-purchase, customer commitments

Why it teaches DDD: This is where teams usually fail: they create one “Product” model that tries to serve everyone. You’ll learn to allow duplication intentionally and translate across contexts.

Core challenges you’ll face:

  • Separating models that share vocabulary but not meaning (maps to bounded contexts)
  • Preventing direct type sharing across contexts (maps to model integrity)
  • Designing a translation boundary (maps to context mapping / ACL)

Key Concepts:

  • Maintaining Model Integrity: “Domain-Driven Design” by Eric Evans — Ch. 14
  • Large-Scale Structure: “Domain-Driven Design” by Eric Evans — Ch. 16

Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Project 1 + 3 strongly recommended

Learning milestones:

  1. You can explain why “shared models” cause subtle bugs
  2. You can keep contexts independent while still integrating
  3. You can evolve one context without refactoring the other

Real World Outcome

You’ll demonstrate that a “Product” change in Catalog does not break Ordering, because Ordering depends on:

  • a stable contract, and/or
  • translated data (not shared types)

Example Output:

$ catalog-cli change-product-name SKU=DESK --name "Standing Desk"
✓ Catalog updated
✓ Published event: ProductDetailsChanged

$ ordering-cli place-order --sku DESK --qty 1
✓ Order placed using snapshot pricing
✓ Ordering stored: "ProductNameAtPurchase" = "Desk" (unchanged by new catalog name)

The Core Question You’re Answering

“When two teams say the same word, do they mean the same thing?”

If they don’t, you need two models—and a translation boundary.

Concepts You Must Understand First

  1. Bounded Context
    • How do you detect boundaries from language and workflows?
    • What does it mean to have separate models?
    • Book Reference: “Domain-Driven Design” — Ch. 14 (Evans)
  2. Context Mapping
    • What are the common relationship patterns (customer/supplier, conformist, ACL)?
    • Book Reference: “Domain-Driven Design” — Ch. 14 (Evans)

Questions to Guide Your Design

  1. Contracts
    • What does Ordering need from Catalog: current price, availability, description?
    • What must be snapshotted at purchase time vs referenced later?
  2. Translation
    • Which fields are “foreign” concepts and should be mapped into local value objects?
    • Where will mapping code live so it doesn’t leak inward?

Thinking Exercise

List 10 attributes that exist in Catalog but should not exist in Ordering (and why). Then list 5 attributes Ordering needs that Catalog doesn’t care about.

The Interview Questions They’ll Ask

  1. “Why is sharing domain models across services a problem?”
  2. “What is a bounded context?”
  3. “What is an anti-corruption layer and when do you use it?”
  4. “How do you prevent a ‘shared kernel’ from becoming a dumping ground?”
  5. “How do you version contracts between contexts?”

Hints in Layers

Hint 1: Starting Point Physically separate contexts into separate modules/packages with no cross-imports of domain types.

Hint 2: Next Level Introduce a thin translation boundary: DTOs at the edge; local value objects inside.

Hint 3: Technical Details Add contract tests: given a Catalog message payload, Ordering translation produces valid local objects.

Hint 4: Tools/Debugging Track “leakage” by searching for Catalog terms inside Ordering; treat leaks as design bugs.

Books That Will Help

Topic Book Chapter
Maintaining boundaries “Domain-Driven Design” by Eric Evans Ch. 14
Large-scale structure “Domain-Driven Design” by Eric Evans Ch. 16

Project 5: Domain Events + Outbox (Reliable Event Publication)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 4: Portfolio-Worthy
  • Business Potential: 3. The “SaaS-in-Waiting”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Reliability / Event-Driven Systems
  • Software or Tool: SQLite/Postgres (optional), background worker, message format
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A reliable mechanism that persists domain events alongside aggregate changes, then publishes them asynchronously (the “outbox pattern”) so events are not lost.

Why it teaches DDD: Domain events are powerful, but naïve event publication causes inconsistencies. This project forces you to confront reliability: persistence, retries, idempotency, ordering—while keeping the domain model clean.

Core challenges you’ll face:

  • Persisting state change + events atomically (maps to consistency boundaries)
  • Designing idempotent event handlers (maps to event-driven evolution)
  • Separating domain events from integration events (maps to bounded contexts)

Key Concepts:

  • Isolating the Domain: “Domain-Driven Design” by Eric Evans — Ch. 4
  • Maintaining Model Integrity: “Domain-Driven Design” by Eric Evans — Ch. 14

Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Project 3 recommended; basic DB knowledge

Learning milestones:

  1. You can explain why “save to DB then publish event” is unsafe
  2. You can publish events reliably without polluting the domain with infrastructure details
  3. You can demonstrate idempotent processing with retries and duplicates

Real World Outcome

You’ll be able to:

  • place an order (or any domain change),
  • kill the process mid-flight,
  • restart,
  • and still see events published exactly once from the publisher’s perspective (with at-least-once delivery + idempotent consumption).

Example Output:

$ ordering-cli place-order --sku DESK --qty 1
✓ Order saved (ord_778)
✓ Outbox appended (2 events)

$ outbox-worker run
→ Publishing OrderPlaced (id=evt_01)
→ Publishing OrderConfirmed (id=evt_02)
✓ Marked 2 events as published

# Simulate failure mid-publish
$ outbox-worker run --crash-after 1
→ Publishing OrderPlaced (id=evt_03)
✗ Crash simulated

$ outbox-worker run
→ Skipping already-published OrderPlaced (id=evt_03) [idempotent]
→ Publishing OrderConfirmed (id=evt_04)
✓ Marked remaining events as published

The Core Question You’re Answering

“How do we make the system truthful under failure?”

If your model assumes the happy path, it will drift into lies under real-world outages.

Concepts You Must Understand First

  1. Domain Events
    • What makes something a domain event (fact) vs a command (intent)?
    • Book Reference: “Domain-Driven Design” — Ch. 3 (Evans)
  2. Boundaries
    • Why do integrations amplify inconsistency?
    • Book Reference: “Domain-Driven Design” — Ch. 14 (Evans)

Questions to Guide Your Design

  1. Event shape
    • What is the minimal event payload that remains meaningful over time?
    • What must be included for idempotency and traceability?
  2. Publishing
    • How will you detect and retry failures?
    • How will you guarantee you never “lose” an event?

Thinking Exercise

Write a failure timeline:

  1. DB transaction commits
  2. Process crashes before publish
  3. Handler receives duplicate event

For each step, decide:

  • what must be true,
  • what can be eventual,
  • what mechanism restores truth.

The Interview Questions They’ll Ask

  1. “What’s the outbox pattern and why do we need it?”
  2. “How do you handle at-least-once delivery?”
  3. “What is idempotency and how do you implement it?”
  4. “What’s the difference between domain events and integration events?”
  5. “How do you handle event ordering and versioning?”

Hints in Layers

Hint 1: Starting Point Persist domain events as part of aggregate save, even in an in-memory demo.

Hint 2: Next Level Build a separate worker that reads unpublished events and marks them published.

Hint 3: Technical Details Use deduplication keys at consumers; don’t rely on perfect “exactly once”.

Hint 4: Tools/Debugging Add a chaos flag to simulate crashes, duplicates, and replays; treat it as a required test mode.

Books That Will Help

Topic Book Chapter
Isolating the domain from infrastructure “Domain-Driven Design” by Eric Evans Ch. 4
Maintaining integrity across boundaries “Domain-Driven Design” by Eric Evans Ch. 14

Project 6: Process Manager / Saga — Returns + Refunds Workflow

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 4: Portfolio-Worthy
  • Business Potential: 3. The “SaaS-in-Waiting”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Workflow / Distributed Consistency
  • Software or Tool: Message queue simulation, timers, persistence (optional)
  • Main Book: “Implementing Domain-Driven Design” by Vaughn Vernon

What you’ll build: A return/refund workflow that coordinates multiple aggregates/contexts (Ordering, Payments, Inventory) through events—without creating one “god aggregate”.

Why it teaches DDD: Real business processes are multi-step and cross-team. This project teaches you to coordinate with a process manager that reacts to events and issues commands—keeping aggregates small and consistent.

Core challenges you’ll face:

  • Modeling long-running workflows without distributed transactions (maps to bounded contexts)
  • Handling partial failures and compensations (maps to reliability)
  • Making workflow state explicit and inspectable (maps to supple design)

Key Concepts:

  • Maintaining Model Integrity: “Domain-Driven Design” by Eric Evans — Ch. 14
  • Large-Scale Structure: “Domain-Driven Design” by Eric Evans — Ch. 16

Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Project 5 strongly recommended

Learning milestones:

  1. You can coordinate multiple steps without “reaching into” other aggregates
  2. You can explain compensation vs retry vs manual intervention
  3. You can debug workflow state from logs/events, not guesswork

Real World Outcome

You’ll run workflow simulations like:

Example Output:

$ return-sim start --order ord_778 --items 1 --reason "damaged"
✓ ReturnRequested (ret_101)
→ Saga created: ReturnRefundSaga(ret_101)

→ Command: IssueReturnLabel(order=ord_778)
✓ Event: ReturnLabelIssued(label=LBL_55)

→ Command: ReceiveReturnedItems(ret_101)
✓ Event: ItemsReceived(ret_101)

→ Command: RefundPayment(order=ord_778, amount=199.99)
✗ Event: RefundFailed(reason="payment gateway timeout")

→ Saga state: WAITING_FOR_REFUND_RETRY
→ Retrying in 5 minutes...

The Core Question You’re Answering

“How do we keep truth across multiple systems without pretending we have one big transaction?”

Sagas are a pragmatic answer: coordinate truth through events, time, and compensation.

Concepts You Must Understand First

  1. Bounded Contexts
    • Why do cross-context invariants become workflows?
    • Book Reference: “Domain-Driven Design” — Ch. 14 (Evans)
  2. Domain Events
    • How do events help workflows evolve safely?
    • Book Reference: “Domain-Driven Design” — Ch. 3 (Evans)

Questions to Guide Your Design

  1. State
    • What states does the saga need (and which are just logs)?
    • How will you persist saga state so a crash doesn’t lose the workflow?
  2. Compensations
    • What do you do when refund fails but items were received?
    • When do you escalate to humans?

Thinking Exercise

Draw two timelines:

  1. Happy path (everything succeeds)
  2. Failure path (refund fails after receiving items)

For each, list:

  • events produced,
  • commands issued,
  • compensation decisions.

The Interview Questions They’ll Ask

  1. “What’s a saga/process manager and when do you use it?”
  2. “How do you handle distributed consistency without 2PC?”
  3. “What is a compensating transaction?”
  4. “How do you design idempotent workflow steps?”
  5. “How do you make workflows observable and debuggable?”

Hints in Layers

Hint 1: Starting Point Define the saga as a pure state machine: input events -> new state + emitted commands.

Hint 2: Next Level Persist saga state and processed-event IDs so it’s restartable and idempotent.

Hint 3: Technical Details Design for manual intervention: include a “stuck” state with a remediation path.

Hint 4: Tools/Debugging Build a “saga inspector” CLI that prints current saga state and pending steps.

Books That Will Help

Topic Book Chapter
Boundaries and integration “Domain-Driven Design” by Eric Evans Ch. 14
Large-scale coordination “Domain-Driven Design” by Eric Evans Ch. 16

Project 7: Event-Sourced Wallet / Ledger (Truth as a Sequence of Facts)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 4: Portfolio-Worthy
  • Business Potential: 3. The “SaaS-in-Waiting”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Event Sourcing / Financial Domain
  • Software or Tool: Append-only log, snapshots (optional), tests
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A wallet/ledger domain where balance is derived from an append-only event stream (deposits, withdrawals, holds, releases), with strict invariants.

Why it teaches DDD: Some domains are naturally event-based (finance, audits). Event sourcing forces you to think in facts, invariants, rehydration, and versioning—skills that make domain models robust even without full event sourcing in production.

Core challenges you’ll face:

  • Modeling facts vs derived state (maps to domain events)
  • Enforcing invariants with replayed history (maps to aggregates)
  • Handling versioning as rules evolve (maps to evolution over time)

Key Concepts:

  • Life Cycle of a Domain Object: “Domain-Driven Design” by Eric Evans — Ch. 6
  • Supple Design: “Domain-Driven Design” by Eric Evans — Ch. 10

Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Strong testing discipline; Projects 2–5 helpful

Learning milestones:

  1. You can rebuild the wallet state from events deterministically
  2. You can explain how invariants are enforced in an event-sourced aggregate
  3. You can evolve event schemas without rewriting history

Real World Outcome

You’ll be able to run:

Example Output:

$ wallet-cli open --owner cust_123
✓ Wallet opened: wal_001

$ wallet-cli deposit --wallet wal_001 --amount 100.00 USD
✓ Appended event: FundsDeposited(evt_10)

$ wallet-cli withdraw --wallet wal_001 --amount 25.00 USD
✓ Appended event: FundsWithdrawn(evt_11)

$ wallet-cli balance --wallet wal_001
Balance: 75.00 USD
Derived from events: 2

$ wallet-cli replay --wallet wal_001
Rehydrated wallet state from 2 events
Invariant checks: passed

The Core Question You’re Answering

“What if the source of truth is a history of facts, not a mutable row in a database?”

Thinking in “facts” makes models easier to audit, debug, and evolve.

Concepts You Must Understand First

  1. Domain Events
    • How do you define events as facts that remain meaningful later?
    • Book Reference: “Domain-Driven Design” — Ch. 3 (Evans)
  2. Aggregate rehydration
    • How does an aggregate rebuild itself from history?
    • Book Reference: “Domain-Driven Design” — Ch. 6 (Evans)

Questions to Guide Your Design

  1. Events
    • What events exist (deposit, withdraw, hold, release, fee, reversal)?
    • Which events are commands in disguise (avoid that)?
  2. Invariants
    • Can balance go negative? Under what conditions?
    • Are holds counted as reserved funds?

Thinking Exercise

List 8 scenarios that break naive ledger designs:

  • concurrent withdrawals
  • reversing a withdrawal
  • changing rounding rules
  • multi-currency wallets

For each: what event(s) represent the truth, and what derived state(s) change?

The Interview Questions They’ll Ask

  1. “What is event sourcing and why use it?”
  2. “How do you handle schema evolution in event streams?”
  3. “How do you enforce invariants in an event-sourced aggregate?”
  4. “What are snapshots and when do you need them?”
  5. “How do you debug production issues with event history?”

Hints in Layers

Hint 1: Starting Point Make events append-only and immutable; derived state must be purely computed.

Hint 2: Next Level Add version fields to events; design upcasters/migrations conceptually (even if simple).

Hint 3: Technical Details Ensure every event is uniquely identified and deduplicatable; replay must be deterministic.

Hint 4: Tools/Debugging Build a “history viewer” that prints the ledger as a timeline.

Books That Will Help

Topic Book Chapter
Object lifecycle “Domain-Driven Design” by Eric Evans Ch. 6
Supple design “Domain-Driven Design” by Eric Evans Ch. 10

Project 8: Rules as Policies — Eligibility + Pricing Engine (Without Rule Soup)

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 4: Portfolio-Worthy
  • Business Potential: 4. The “Real Business”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Business Rules / Policy Modeling
  • Software or Tool: Scenario runner + test suite
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A policy-based rules engine for a real domain slice (pick one):

  • promotions/discount eligibility
  • insurance claim eligibility
  • loan pre-approval criteria
  • shipping eligibility + surcharges

Why it teaches DDD: This project is exactly your goal: turning messy rules into clean code. You’ll learn to separate stable concepts (value objects, facts) from changeable policies and implement them in a way that remains readable as rules expand.

Core challenges you’ll face:

  • Modeling the “facts” the policy operates on (maps to value objects)
  • Making policies composable and testable (maps to supple design)
  • Preventing rule interactions from producing contradictions (maps to invariants vs policies)

Key Concepts:

  • Making Implicit Concepts Explicit: “Domain-Driven Design” by Eric Evans — Ch. 9
  • Supple Design: “Domain-Driven Design” by Eric Evans — Ch. 10

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2 recommended

Learning milestones:

  1. You can add a new rule without editing 10 existing rules
  2. You can explain policy precedence and conflicts explicitly
  3. You can test rules with scenarios that look like business examples

Real World Outcome

You’ll run a “policy evaluator” against scenario files and get a clear explanation of decisions:

Example Output:

$ policy-eval run scenarios/promo-weekend.json
Scenario: "VIP customer buys 3 items on weekend"
Decision: ELIGIBLE
Applied Policies:
 - VIPEligible
 - WeekendBonus
 - BulkPurchaseThreshold
Result:
 - discountPercent: 15%
 - reason: "VIP + weekend + bulk"

$ policy-eval run scenarios/promo-edge-case.json
Scenario: "Employee purchase with coupon"
Decision: INELIGIBLE
Rejected By:
 - EmployeeExclusionPolicy
Reason: "Employee discounts cannot be combined with coupons"

The Core Question You’re Answering

“How do we represent changeable business policy without turning it into a fragile maze of conditionals?”

If policies are opaque, the system becomes unmaintainable and the business loses trust.

Concepts You Must Understand First

  1. Policy vs Invariant
    • Which rules can change next quarter?
    • Which rules are safety/consistency constraints?
    • Book Reference: “Domain-Driven Design” — Ch. 10 (Evans)
  2. Expressive Models
    • How do you model “facts” so policies read like business?
    • Book Reference: “Domain-Driven Design” — Ch. 9 (Evans)

Questions to Guide Your Design

  1. Explainability
    • How will you explain why a scenario was eligible/ineligible?
    • How will you show which policy “won” when conflicts occur?
  2. Composition
    • Can policies be combined as AND/OR/NOT logic?
    • How do you handle precedence?

Thinking Exercise

Write 15 policy statements in plain English, then group them:

  • eligibility policies
  • pricing adjustment policies
  • exclusion policies
  • precedence rules

Now ask: which groups belong in different bounded contexts?

The Interview Questions They’ll Ask

  1. “How do you model complex business rules without conditionals everywhere?”
  2. “What’s the difference between a specification and a policy?”
  3. “How do you keep rules testable and explainable?”
  4. “How do you handle conflicting business rules?”
  5. “How do you evolve rules without breaking existing behavior?”

Hints in Layers

Hint 1: Starting Point Start with scenario-based tests; force each policy to be independently testable.

Hint 2: Next Level Make every policy return both a decision and an explanation trail.

Hint 3: Technical Details Separate “facts” from “policies”: facts are stable types; policies are replaceable objects/functions.

Hint 4: Tools/Debugging Build a decision trace printout; never settle for “false” with no reason.

Books That Will Help

Topic Book Chapter
Making concepts explicit “Domain-Driven Design” by Eric Evans Ch. 9
Supple design for rules “Domain-Driven Design” by Eric Evans Ch. 10

Project 9: Refactor a Legacy “Rules Soup” Module into a Domain Model

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Java
  • Coolness Level: Level 5: “I Can’t Believe You Built This”
  • Business Potential: 4. The “Real Business”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Refactoring / Domain Insight
  • Software or Tool: Tests + golden master snapshots + diff tools
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A deliberately messy “legacy” module (or a provided messy set of rules) that calculates outcomes (e.g., shipping fees, refund eligibility, subscription proration), then refactor it into:

  • explicit value objects,
  • explicit policies,
  • an aggregate (if appropriate),
  • and clear boundaries.

Why it teaches DDD: DDD is not only greenfield modeling. Most teams adopt it by refactoring. This project teaches the most valuable skill: changing structure while preserving behavior, then evolving behavior safely.

Core challenges you’ll face:

  • Freezing behavior with a “golden master” test harness (maps to reliability)
  • Discovering implicit domain concepts in messy code (maps to deeper insight)
  • Refactoring without breaking invariants (maps to model integrity)

Key Concepts:

  • Refactoring Toward Deeper Insight: “Domain-Driven Design” by Eric Evans — Ch. 13
  • Making Implicit Concepts Explicit: “Domain-Driven Design” by Eric Evans — Ch. 9

Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Strong testing discipline; Projects 2 and 8 helpful

Learning milestones:

  1. You can preserve behavior while changing structure
  2. You can name and model implicit concepts you discovered
  3. You can add a new rule without destabilizing the module

Real World Outcome

You’ll produce:

  1. A baseline behavior snapshot (“golden master”)
  2. A refactored domain model that produces identical results for old inputs
  3. A changelog showing how adding a new rule becomes safer and more localized

Example Output:

$ legacy-rule-harness generate --cases 5000 --seed 42
✓ Generated 5,000 input cases
✓ Wrote ./golden-master.json

$ legacy-rule-harness verify --impl legacy
✓ 5,000 / 5,000 cases match golden master

$ legacy-rule-harness verify --impl refactored
✓ 5,000 / 5,000 cases match golden master

$ legacy-rule-harness diff --from legacy --to refactored --show-explanations
No behavioral diffs. Structural diffs: 18 new domain concepts introduced.

The Core Question You’re Answering

“How do we evolve a system safely when the real rules are buried in accidental complexity?”

DDD is often about reclaiming meaning from legacy code.

Concepts You Must Understand First

  1. Deeper Insight
    • What does it mean to refactor the model, not just the code?
    • Book Reference: “Domain-Driven Design” — Ch. 13 (Evans)
  2. Explicit Concepts
    • How do you detect “missing” value objects/policies in a rules soup?
    • Book Reference: “Domain-Driven Design” — Ch. 9 (Evans)

Questions to Guide Your Design

  1. Golden master
    • How do you generate representative test cases?
    • How do you ensure determinism and reproducibility?
  2. Naming
    • What names emerge from the business, not from your implementation?
    • Which names are overloaded and need context boundaries?

Thinking Exercise

Take 20 if statements from the legacy module and label each as:

  • invariant
  • policy
  • workflow decision
  • technical concern (parsing, formatting, IO)

Then ask: which parts can become value objects? which become domain services?

The Interview Questions They’ll Ask

  1. “How do you introduce DDD into a legacy codebase?”
  2. “What is a golden master test and when is it useful?”
  3. “How do you refactor toward deeper insight?”
  4. “How do you prevent domain logic from leaking into infrastructure?”
  5. “How do you keep refactoring safe under changing requirements?”

Hints in Layers

Hint 1: Starting Point Freeze behavior first; don’t refactor blind.

Hint 2: Next Level Refactor by extracting types and names, not by reorganizing folders.

Hint 3: Technical Details Introduce value objects and policies gradually; keep compatibility layers temporarily.

Hint 4: Tools/Debugging Use diffs and trace output: every decision should be explainable, even in legacy mode.

Books That Will Help

Topic Book Chapter
Deeper insight refactoring “Domain-Driven Design” by Eric Evans Ch. 13
Making implicit concepts explicit “Domain-Driven Design” by Eric Evans Ch. 9

Project 10: Domain Test Harness — Scenario Tests + Property-Based Invariants

  • File: DDD_TYPESCRIPT_LEARNING_PROJECTS.md
  • Main Programming Language: TypeScript
  • Alternative Programming Languages: Kotlin, C#, Rust
  • Coolness Level: Level 3: Actually Impressive
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Testing / Reliability
  • Software or Tool: Test runner + property-based testing library (optional)
  • Main Book: “Domain-Driven Design” by Eric Evans

What you’ll build: A reusable test harness that can run:

  • scenario tests written as JSON/YAML (“given/when/then” narratives), and
  • property-based tests for invariants (e.g., totals are never negative, states don’t skip illegally).

Why it teaches DDD: DDD succeeds when invariants are continuously enforced. This project makes testing part of the domain: scenarios become executable specs, and properties become “always true” guards against future regressions.

Core challenges you’ll face:

  • Writing tests that look like business narratives (maps to ubiquitous language)
  • Proving invariants across many random sequences (maps to invariants)
  • Keeping tests independent from infrastructure (maps to isolating the domain)

Key Concepts:

  • Communication and Language: “Domain-Driven Design” by Eric Evans — Ch. 2
  • Isolating the Domain: “Domain-Driven Design” by Eric Evans — Ch. 4

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Comfortable writing tests; any prior project

Learning milestones:

  1. You can express domain behavior as scenarios, not unit-test minutiae
  2. You can detect invariant violations automatically across random command sequences
  3. You can refactor internals freely while keeping domain behavior stable

Real World Outcome

You’ll run:

Example Output:

$ domain-test run scenarios/order-refund.yaml
✓ Scenario: "Refund after shipment is rejected"
✓ Steps: 9
✓ Domain reasons match expected explanations

$ domain-test property --aggregate Order --runs 1000
✓ Checked 1,000 random command sequences
✓ Invariants held:
 - total >= 0
 - cannot ship unless paid
 - cannot refund more than paid

The Core Question You’re Answering

“How do we keep a domain model correct as it changes?”

Tests are the mechanism by which “meaning” survives refactoring and new features.

Concepts You Must Understand First

  1. Executable Specifications
    • How do scenario tests differ from low-level unit tests?
    • Book Reference: “Domain-Driven Design” — Ch. 2 (Evans)
  2. Isolating the Domain
    • Why must tests avoid direct DB/API dependencies at the domain level?
    • Book Reference: “Domain-Driven Design” — Ch. 4 (Evans)

Questions to Guide Your Design

  1. Scenario format
    • What is the minimal schema to express a scenario?
    • How do you represent expected errors as domain reasons?
  2. Properties
    • Which invariants should never be violated under any command sequence?
    • How do you generate realistic command sequences, not nonsense?

Thinking Exercise

Pick one aggregate and write:

  • 5 scenario tests (happy + failure)
  • 3 invariants that should hold under random sequences

Then ask: which invariants belong in the model, and which belong in application policy?

The Interview Questions They’ll Ask

  1. “How do you test a domain model effectively?”
  2. “What makes a good invariant test?”
  3. “How do you write tests that communicate with non-engineers?”
  4. “What is property-based testing and when would you use it?”
  5. “How do you keep tests from becoming brittle?”

Hints in Layers

Hint 1: Starting Point Start with scenario tests as data files; keep them readable to humans.

Hint 2: Next Level Make domain failures return structured reasons so tests can assert meaning, not just error strings.

Hint 3: Technical Details Add a command generator that respects basic preconditions so sequences are meaningful.

Hint 4: Tools/Debugging When a property test fails, print the smallest command sequence that reproduces it.

Books That Will Help

Topic Book Chapter
Language and collaboration “Domain-Driven Design” by Eric Evans Ch. 2
Isolating the domain “Domain-Driven Design” by Eric Evans Ch. 4

Project Comparison

Project Difficulty Time Depth of Understanding Fun Factor
Ubiquitous Language Workshop Kit Beginner Weekend High (strategic) Medium
Value Object Toolkit Intermediate 1-2 weeks High (tactical) Medium
Ordering Aggregate Intermediate 1-2 weeks Very High High
Catalog vs Ordering Contexts Advanced 1 month+ Very High Medium
Domain Events + Outbox Advanced 1 month+ Very High High
Saga: Returns + Refunds Advanced 1 month+ Very High High
Event-Sourced Wallet Advanced 1 month+ Very High High
Policy-Based Rules Engine Intermediate 1-2 weeks High High
Refactor Rules Soup Advanced 1 month+ Extremely High Medium
Domain Test Harness Intermediate 1-2 weeks High Medium

Recommendation

If your goal is turning messy rules into clean code, start in this order:

  1. Project 1 (language + boundaries): prevents you from modeling the wrong thing.
  2. Project 2 (value objects): removes primitive chaos and makes rules enforceable.
  3. Project 8 (policies): directly attacks “rules soup” with composable policy design.
  4. Project 3 (aggregate): forces consistency decisions and state transitions.
  5. Then choose a scaling track:
    • Reliability track: Projects 5 → 6
    • Model evolution track: Project 9
    • Auditability track: Project 7

Final Overall Project: Multi-Context Commerce System (Ordering + Billing + Fulfillment + Support)

Build a small-but-real system with at least four bounded contexts:

  • Ordering: orders, state transitions, snapshot pricing, refunds requests
  • Billing: invoices, payments, refunds, credit notes
  • Fulfillment: shipments, tracking, warehouse workflow
  • Support: tickets, customer communications, return authorization

Required characteristics:

  • Each context has its own model (no shared domain types).
  • Integrations happen via explicit contracts + translation (ACL).
  • Aggregates enforce invariants locally; cross-context workflows use events + a saga.
  • A scenario test suite demonstrates at least 20 business narratives end-to-end.

What success looks like:

$ demo run "customer places order, pays, ships, then requests refund"
✓ Ordering: OrderPlaced -> OrderConfirmed -> PaymentCaptured -> OrderReadyToShip
✓ Fulfillment: ShipmentCreated -> Shipped
✓ Support: TicketOpened -> ReturnAuthorized
✓ Billing: RefundIssued -> CreditNoteCreated
✓ All contexts consistent; no forbidden transitions occurred

Summary (All Projects)

  1. Ubiquitous Language Workshop Kit (glossary + scenarios + context map)
  2. TypeScript Value Object Toolkit (validation + normalization + equality)
  3. Aggregate Modeling: Ordering + Payments (invariants first)
  4. Bounded Context Split: Catalog vs Ordering (translation boundaries)
  5. Domain Events + Outbox (reliable publication)
  6. Process Manager / Saga: Returns + Refunds workflow
  7. Event-Sourced Wallet / Ledger (truth as facts)
  8. Rules as Policies: Eligibility + Pricing engine (no rule soup)
  9. Refactor Legacy Rules Soup into a domain model (deeper insight)
  10. Domain Test Harness (scenarios + property-based invariants)