Skip to main content
โšก Calmops

Domain-Driven Design: Building Domain-Centric Applications

Introduction

Domain-Driven Design (DDD) is an approach to software development that focuses on the core domain and domain logic. It helps tackle complexity in systems with rich business rules by aligning code structure with business concepts.

Core Building Blocks

Entities and Value Objects

from dataclasses import dataclass
from datetime import datetime
from typing import Optional
import uuid

class Entity:
    """Base entity with identity."""
    
    def __init__(self, id: str = None):
        self.id = id or str(uuid.uuid4())
    
    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return False
        return self.id == other.id
    
    def __hash__(self):
        return hash(self.id)

@dataclass(frozen=True)
class Address:
    """Value object - immutable and defined by values."""
    street: str
    city: str
    state: str
    zip_code: str
    country: str

class User(Entity):
    """Entity with business logic."""
    
    def __init__(self, email: str, name: str):
        super().__init__()
        self._email = email
        self._name = name
        self._created_at = datetime.utcnow()
        self._is_active = True
    
    @property
    def email(self) -> str:
        return self._email
    
    @property
    def name(self) -> str:
        return self._name
    
    @property
    def is_active(self) -> bool:
        return self._is_active
    
    def activate(self):
        self._is_active = True
    
    def deactivate(self):
        self._is_active = False
    
    def change_email(self, new_email: str):
        if "@" not in new_email:
            raise ValueError("Invalid email")
        self._email = new_email

Aggregates

class OrderItem:
    """Part of an aggregate."""
    
    def __init__(self, product_id: str, quantity: int, unit_price: float):
        self.product_id = product_id
        self.quantity = quantity
        self.unit_price = unit_price
    
    @property
    def total(self) -> float:
        return self.quantity * self.unit_price

class Order(AggregateRoot):
    """Aggregate root - controls access to aggregate."""
    
    def __init__(self, user_id: str):
        super().__init__()
        self._user_id = user_id
        self._items: List[OrderItem] = []
        self._status = "pending"
    
    def add_item(self, product_id: str, quantity: int, unit_price: float):
        item = OrderItem(product_id, quantity, unit_price)
        self._items.append(item)
    
    def submit(self):
        if not self._items:
            raise ValueError("Cannot submit empty order")
        self._status = "submitted"
    
    def cancel(self):
        if self._status == "shipped":
            raise ValueError("Cannot cancel shipped order")
        self._status = "cancelled"
    
    @property
    def total(self) -> float:
        return sum(item.total for item in self._items)

Domain Events

from dataclasses import dataclass
from datetime import datetime

@dataclass
class DomainEvent:
    """Domain event - something that happened."""
    event_id: str
    event_type: str
    occurred_at: datetime
    aggregate_id: str

@dataclass
class OrderSubmitted(DomainEvent):
    order_id: str
    user_id: str
    total: float

class Order(AggregateRoot):
    """Aggregate that publishes events."""
    
    def __init__(self, user_id: str):
        super().__init__()
        self._user_id = user_id
        self._items = []
        self._status = "pending"
        self._events = []
    
    def submit(self):
        self._status = "submitted"
        
        event = OrderSubmitted(
            event_id=str(uuid.uuid4()),
            event_type="order.submitted",
            occurred_at=datetime.utcnow(),
            aggregate_id=self.id,
            order_id=self.id,
            user_id=self._user_id,
            total=self.total
        )
        self._events.append(event)
    
    def pull_events(self) -> List[DomainEvent]:
        events = self._events.copy()
        self._events.clear()
        return events

Conclusion

DDD helps align code with business concepts. Use entities for things with identity, value objects for immutable values, aggregates for consistency boundaries, and domain events for communication. Start with simple domain models and evolve as understanding grows.

Resources

  • “Domain-Driven Design” by Eric Evans
  • “Implementing Domain-Driven Design” by Vaughn Vernon

Comments