ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Domain-Driven Design

Design software systems that model complex business domains. Covers bounded contexts, aggregates, value objects, domain events, context mapping, and the patterns that align code structure with business understanding.

Domain-Driven Design (DDD) is a set of principles for building software that accurately models complex business domains. Instead of starting with a database schema or API endpoints, DDD starts with understanding the business domain and encoding that understanding directly into the code.


Strategic Design

Bounded Context:
  A linguistic boundary where a term has a specific meaning
  
  Example: "Account" means different things in different contexts
  
  Banking Context:     Account = financial account (balance, transactions)
  Identity Context:    Account = user account (email, password, profile)
  Marketing Context:   Account = customer account (segments, campaigns)
  
  Each bounded context has its own model, its own code, its own database
  They communicate through well-defined interfaces

Context Mapping

                    ┌──────────────┐
                    │   Banking    │
                    │   Context    │
                    │              │
                    │ Account =    │
                    │ {balance,    │
                    │  txns}       │
                    └──────┬───────┘
                           │ ACL (Anti-Corruption Layer)
                           │ Translates between models
                    ┌──────┴───────┐
                    │  Identity    │
                    │  Context     │
                    │              │
                    │ Account =    │
                    │ {email,      │
                    │  password}   │
                    └──────────────┘

Relationship Patterns:
  Shared Kernel:      Two contexts share a subset of model
  Customer-Supplier:  Upstream serves downstream's needs
  Conformist:         Downstream conforms to upstream's model
  ACL:                Downstream translates upstream's model
  Published Language: Both agree on a shared interchange format
  Separate Ways:      No integration (duplicate data)

Tactical Patterns

Entities

class Order:
    """Entity: identified by ID, has lifecycle."""
    def __init__(self, order_id: OrderId, customer_id: CustomerId):
        self.id = order_id
        self.customer_id = customer_id
        self.items: list[OrderItem] = []
        self.status = OrderStatus.DRAFT
        self._events: list[DomainEvent] = []
    
    def add_item(self, product: Product, quantity: int):
        if self.status != OrderStatus.DRAFT:
            raise OrderError("Cannot add items to non-draft order")
        
        item = OrderItem(product.id, product.price, quantity)
        self.items.append(item)
        self._events.append(ItemAdded(self.id, item))
    
    def submit(self):
        if not self.items:
            raise OrderError("Cannot submit empty order")
        
        self.status = OrderStatus.SUBMITTED
        self._events.append(OrderSubmitted(self.id, self.total))
    
    @property
    def total(self) -> Money:
        return sum(item.subtotal for item in self.items)

Value Objects

@dataclass(frozen=True)  # Immutable
class Money:
    """Value Object: identified by value, not by ID."""
    amount: Decimal
    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)
    
    def __mul__(self, quantity: int) -> "Money":
        return Money(self.amount * quantity, self.currency)

@dataclass(frozen=True)
class Address:
    street: str
    city: str
    state: str
    zip_code: str
    country: str

Domain Events

@dataclass(frozen=True)
class OrderSubmitted:
    """Domain Event: something that happened in the domain."""
    order_id: str
    total: Money
    occurred_at: datetime = field(default_factory=datetime.utcnow)

class OrderEventHandler:
    def handle(self, event: OrderSubmitted):
        # Trigger side effects in other bounded contexts
        self.inventory_service.reserve_items(event.order_id)
        self.notification_service.send_confirmation(event.order_id)
        self.analytics_service.record_order(event.order_id, event.total)

Anti-Patterns

Anti-PatternConsequenceFix
Anemic domain modelBusiness logic in services, entities are data bagsRich domain model with behavior
Shared database across contextsTight coupling, schema conflictsDatabase per bounded context
God entity (mega-aggregate)Performance, concurrency issuesSmall aggregates, consistency boundaries
No ubiquitous languageDevelopers and business speak different languagesShared vocabulary in code AND conversation
DDD everywhereOver-engineering simple domainsDDD for complex cores, CRUD for simple subdomains

DDD is not about patterns — it is about deeply understanding the business domain and encoding that understanding into code that evolves with the business.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →