Skip to main content
Settings
Search
Appearance
Theme Mode
About
Jekyll v3.10.0
Environment Production
Last Build
2026-06-18 05:38 UTC
Current Environment Production
Build Time Jun 18, 05:38
Jekyll v3.10.0
Build env (JEKYLL_ENV) production
Quick Links
Page Location
Page Info
Layout quest
Collection quests
Path _quests/1110/domain-driven-design.md
URL /quests/1110/domain-driven-design/
Date 2025-11-29
Theme Skin
SVG Backgrounds
Layer Opacity
0.6
0.04
0.08

Domain-Driven Design: Modeling the Business in Code

Master DDD: ubiquitous language, entities and value objects, aggregates, and bounded contexts so your code models the business it serves.

Table of Contents

Lvl 1110Master 🏰 Main Quest 🔴 Hard 3-4 hours

Domain-Driven Design: Modeling the Business in Code

Build software whose model speaks the language of the business using DDD’s tactical and strategic patterns

Primary Tech
🛠️ python
Skill Focus
Fullstack
Series
System Design Mastery
Author
IT-Journey Team
XP Range
⚡ 8000-9000

Welcome back to the Citadel, Architect. You have learned to shape objects with patterns; now you must learn to shape the meaning the objects carry. Domain-Driven Design is the discipline of letting the business domain - not the database, not the framework - drive the design of your software. It is how a payments team and a payments codebase come to speak exactly the same language.

Whether your last project drowned in a “God service” that knew everything, or you have watched two teams argue because “customer” meant different things to each, this quest forges the tools to carve a complex domain into models that stay clean as they grow.

📖 The Legend Behind This Quest

In 2003, Eric Evans gave a name to a craft that great engineers practiced by instinct: aligning the model in the code with the model in the experts’ heads. He observed that the costliest defects are not bugs in algorithms but mistranslations - the moment a developer guesses what “settlement” means and guesses wrong.

DDD’s remedy is twofold. Tactical patterns (entities, value objects, aggregates, repositories) give you a precise vocabulary for the building blocks. Strategic patterns (bounded contexts, context maps, the ubiquitous language) keep large systems from collapsing into one tangled model. Master both and your microservices - which you will design next - will have boundaries that actually make sense.

🎯 Quest Objectives

By the time you complete this epic journey, you will have mastered:

Primary Objectives (Required for Quest Completion)

  • Ubiquitous Language - Build a shared vocabulary that lives in code, tests, and conversation
  • Entities vs. Value Objects - Decide what has identity and what is defined purely by its attributes
  • Aggregates and Invariants - Draw consistency boundaries that protect business rules
  • Bounded Contexts - Split a large domain into models that are internally consistent

Secondary Objectives (Bonus Achievements)

  • Repositories - Abstract persistence behind a domain-shaped interface
  • Domain Events - Capture meaningful business occurrences as first-class objects
  • Context Mapping - Describe how bounded contexts relate (shared kernel, anticorruption layer)

Mastery Indicators

You’ll know you’ve truly mastered this quest when you can:

  • Defend why a given concept is an entity rather than a value object
  • Draw an aggregate boundary and name the invariant it protects
  • Explain why “customer” can legitimately mean two different things in two contexts
  • Write a ubiquitous-language glossary that a domain expert would approve

🗺️ Quest Prerequisites

📋 Knowledge Requirements

  • Comfortable with OO modeling and composition
  • Completed Software Design Patterns (recommended)
  • Have worked on a system with real business rules

🛠️ System Requirements

  • Modern operating system (Windows 10+, macOS 10.14+, or Linux)
  • Python 3.10+ installed
  • A text editor or IDE (VS Code recommended)

🧠 Skill Level Indicators

This 🔴 Hard quest expects:

  • You have seen requirements lost in translation between teams
  • You can model a small domain on a whiteboard
  • Ready for 3-4 hours of focused study

🌍 Choose Your Adventure Platform

DDD is a way of thinking, so the platform matters little. The code samples use Python; the modeling exercises need only paper or a whiteboard.

🍎 macOS Kingdom Path

Click to expand macOS instructions ```bash brew install python@3.12 mkdir -p ~/ddd-quest && cd ~/ddd-quest python3 -m venv .venv && source .venv/bin/activate python --version ```

🪟 Windows Empire Path

Click to expand Windows instructions ```powershell winget install Python.Python.3.12 mkdir ddd-quest; cd ddd-quest python -m venv .venv; .\.venv\Scripts\Activate.ps1 python --version ```

🐧 Linux Territory Path

Click to expand Linux instructions ```bash sudo apt update && sudo apt install -y python3 python3-venv # Debian/Ubuntu mkdir -p ~/ddd-quest && cd ~/ddd-quest python3 -m venv .venv && source .venv/bin/activate python --version ```

☁️ Cloud Realms Path

Click to expand Cloud/Container instructions ```bash docker run -it --rm python:3.12-slim bash python --version ```

🧙‍♂️ Chapter 1: The Ubiquitous Language - One Tongue for All

A bounded context’s first asset is its words. When developers, testers, and domain experts use the same term to mean the same thing, mistranslation - the costliest defect - disappears.

⚔️ Skills You’ll Forge in This Chapter

  • Distilling a glossary from how experts actually speak
  • Letting that language name your classes, methods, and tests
  • Spotting when one word secretly means two things

🏗️ Language That Lives in Code

The ubiquitous language is not documentation you write once. It is the names in your code. If experts say “a policy lapses,” your code says policy.lapse(), not policy.set_status(3).

# ❌ Anemic, framework-shaped — the business vocabulary has vanished
order.status = 2
order.update()

# ✅ Ubiquitous language made executable — the method names ARE the glossary
order.confirm()        # transitions a draft to a confirmed order
order.ship(carrier)    # only legal once confirmed
order.cancel(reason)   # records why, not just that

When a new term appears in a meeting (“backorder,” “settlement window”), it belongs in the glossary first, then in the code. A drifted name is a future bug.

🔍 Knowledge Check: Language

  • Why is order.confirm() better than order.status = 2?
  • What should happen first when a domain expert uses a word you do not have?
  • How does a shared glossary reduce defects?

🧙‍♂️ Chapter 2: Tactical Patterns - Entities, Value Objects, Aggregates

Now we forge the building blocks. The central question for every concept: does it have an identity that persists through change, or is it defined entirely by its values?

⚔️ Skills You’ll Forge in This Chapter

  • Entities vs. value objects
  • Aggregates as consistency boundaries
  • Repositories as the seam to persistence

🏗️ Entities vs. Value Objects

An entity has a thread of identity: a Customer is the same customer even after they change their name. A value object is interchangeable when its attributes match: two Money(10, "USD") instances are equal and immutable.

from dataclasses import dataclass

@dataclass(frozen=True)            # value object: immutable, compared by value
class Money:
    amount: int                    # store minor units (cents) to avoid float drift
    currency: str
    def __add__(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)

class Customer:                    # entity: identity persists through change
    def __init__(self, customer_id: str, name: str):
        self.id = customer_id      # equality is by id, not by attributes
        self.name = name
    def rename(self, new_name: str) -> None:
        self.name = new_name       # still the same customer afterwards

print(Money(500, "USD") + Money(250, "USD"))   # Money(750, 'USD')

🏗️ Aggregates - The Consistency Boundary

An aggregate is a cluster of objects treated as one unit for data changes, with a single root that is the only entry point. The aggregate guards an invariant - a rule that must always hold.

class OrderLine:
    def __init__(self, sku: str, qty: int):
        self.sku, self.qty = sku, qty

class Order:                       # aggregate root
    MAX_LINES = 50
    def __init__(self, order_id: str):
        self.id = order_id
        self._lines: list[OrderLine] = []   # internals are private
    def add_line(self, sku: str, qty: int) -> None:
        # Invariant enforced HERE, at the root — nowhere else may mutate lines
        if len(self._lines) >= self.MAX_LINES:
            raise ValueError("An order may not exceed 50 lines")
        if qty <= 0:
            raise ValueError("Quantity must be positive")
        self._lines.append(OrderLine(sku, qty))
    @property
    def line_count(self) -> int:
        return len(self._lines)

Rule of thumb: keep aggregates small, reference other aggregates by id (not by object), and load/save each aggregate as a whole through a repository.

from abc import ABC, abstractmethod

class OrderRepository(ABC):        # domain-shaped persistence seam
    @abstractmethod
    def get(self, order_id: str) -> Order: ...
    @abstractmethod
    def save(self, order: Order) -> None: ...

🔍 Knowledge Check: Tactical Patterns

  • Is an Address usually an entity or a value object? Why?
  • Why must all changes to order lines go through the Order root?
  • Why reference other aggregates by id rather than holding the object?

🧙‍♂️ Chapter 3: Strategic Design - Bounded Contexts and Context Maps

One model cannot serve an entire enterprise. A bounded context is an explicit boundary within which a model and its language are consistent. Cross the boundary and the same word may mean something new.

⚔️ Skills You’ll Forge in This Chapter

  • Carving a domain into bounded contexts
  • Reading and drawing a context map
  • The anticorruption layer

🏗️ Why “Customer” Means Two Things

In Sales, a Customer is a lead with a pipeline stage. In Shipping, a Customer is an address and a delivery preference. Forcing both into one bloated Customer class is how models rot. Bounded contexts let each model be exactly as rich as its own job requires.

graph LR
    subgraph Sales Context
        S_Customer[Customer<br/>= lead + pipeline]
        Opportunity
    end
    subgraph Shipping Context
        Sh_Customer[Customer<br/>= address + prefs]
        Shipment
    end
    subgraph Billing Context
        B_Customer[Customer<br/>= account + terms]
        Invoice
    end
    S_Customer -. shared id .-> Sh_Customer
    Sh_Customer -. shared id .-> B_Customer
    style S_Customer fill:#2196F3,color:#fff
    style Sh_Customer fill:#FF9800,color:#fff
    style B_Customer fill:#4CAF50,color:#fff

A context map documents the relationships between contexts - shared kernel, customer/supplier, or an anticorruption layer (ACL) that translates an external model into yours so a foreign model never leaks in. These boundaries are the natural seams along which you will later split microservices.

🔍 Knowledge Check: Strategic Design

  • Why is one giant Customer model a liability across an enterprise?
  • What does an anticorruption layer protect you from?
  • How do bounded contexts foreshadow service boundaries?

🎮 Mastery Challenges

🟢 Novice Challenge: Build a Glossary

Objective: Pick a domain you know (a library, a gym, a game). Write a 10-term ubiquitous-language glossary.

Requirements:

  • Each term is one a domain expert would actually use
  • Mark which terms are entities and which are value objects
  • Flag any term that means two things in two parts of the domain

Validation: A non-developer in that domain agrees the definitions are correct.

🟡 Intermediate Challenge: Draw an Aggregate

Objective: Model one aggregate from your domain in code, enforcing one real invariant at the root.

Requirements:

  • An aggregate root with private internals
  • At least one value object
  • An invariant that the root enforces and tests prove

Validation: A test demonstrates the invariant cannot be violated from outside.

🔴 Advanced Challenge: Context Map

Objective: Decompose your domain into 3+ bounded contexts and draw the context map.

Requirements:

  • Name each context and its core model
  • Identify one term that legitimately differs across contexts
  • Mark at least one relationship as an anticorruption layer and explain why

Validation: Each context’s model is internally consistent and minimal.

🏆 Quest Rewards & Achievements

🎖️ Badges Earned:

  • 🏆 Domain Cartographer - You can carve a business into clean bounded contexts
  • 🗣️ Speaker of the Ubiquitous Tongue - Your code and your conversations use one language

🛠️ Skills Unlocked:

  • Tactical Modeling - Entities, value objects, aggregates, repositories
  • Strategic Design - Bounded contexts and context mapping

🔓 Unlocked Quests:

  • Microservices Architecture - Turn bounded contexts into deployable services
  • Event-Driven Design - Let domain events flow between contexts

📊 Progression Points: +90 XP

🗺️ Next Steps in Your Journey

Continue the Main Story:

Explore Side Adventures:

Character Class Recommendations

💻 Software Developer: Continue to Microservices Architecture
🏗️ System Engineer: Explore Event-Driven Design
📊 Data Scientist: Note how bounded contexts clarify which data is authoritative where

📚 Resources

Official Documentation

Community Resources

Learning Materials

🤝 Quest Completion Checklist

  • ✅ Completed all primary objectives
  • ✅ Wrote a ubiquitous-language glossary for a real domain
  • ✅ Answered all knowledge check questions
  • ✅ Completed at least one mastery challenge
  • ✅ Explored the resource library
  • ✅ Identified your next quest in the journey

🕸️ Knowledge Graph

Structured wiki-links connect this quest to the IT-Journey knowledge graph. Open the Obsidian Graph View to explore connections.

Level hub: [[Level 1110 - Architecture & Design Patterns]] Overworld: [[🏰 Overworld - Master Quest Map]] Prerequisites: [[Software Design Patterns: Gang of Four and Modern Patterns]] Unlocks: [[Microservices Architecture: Decomposing the Monolith]] · [[Event-Driven Design: Pub/Sub, Event Sourcing, and CQRS]] Obsidian docs: [[Obsidian Knowledge Graph and Wiki Links]]

🎁 Rewards

90 XP

Badges

  • 🏆 Domain Cartographer - Mapped a business into bounded contexts
  • 🗣️ Speaker of the Ubiquitous Tongue - Aligned code and conversation

Skills unlocked

  • 🛠️ Tactical Modeling (entities, value objects, aggregates)
  • 🧠 Strategic Design (bounded contexts, context mapping)

Features unlocked

  • Strategic design foundation for the rest of the Citadel

🕸️ Quest Network

graph TD loading(["Loading quest graph…"])