بنقرة واحدة
domain-driven-design
Apply DDD tactical patterns (Entities, Value Objects, Aggregates, Domain Services, Repositories) and strategic design (Ubiquitous Language, Bounded Contexts). Use when modeling complex business logic.
القائمة
Apply DDD tactical patterns (Entities, Value Objects, Aggregates, Domain Services, Repositories) and strategic design (Ubiquitous Language, Bounded Contexts). Use when modeling complex business logic.
Apply Clean Architecture layering and dependency rules (Domain, Application, Infrastructure, Presentation layers). Use when structuring applications or ensuring dependencies point inward.
Write clear, testable requirements using User Stories and Gherkin scenarios. Capture functional and non-functional requirements with proper acceptance criteria. Use when defining new features or documenting system behavior.
Enforce SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) in object-oriented design. Use when writing or reviewing classes and modules.
Follow TDD practices with Red-Green-Refactor cycle. Write tests before implementation, use AAA pattern, and ensure high-quality test coverage. Use when implementing features or fixing bugs.
Follow trunk-based development practices with short-lived branches, frequent integration to main, feature flags, and continuous integration. Use when managing git workflow and releases.
| name | domain-driven-design |
| description | Apply DDD tactical patterns (Entities, Value Objects, Aggregates, Domain Services, Repositories) and strategic design (Ubiquitous Language, Bounded Contexts). Use when modeling complex business logic. |
You are assisting with code that must follow Domain-Driven Design principles.
Focus on the Domain: The heart of software is its domain model - the conceptual model of the problem domain that incorporates both behavior and data. DDD enables developers to translate complex problem domains into rich, expressive, and evolving software.
When to Use DDD:
When NOT to Use DDD:
The foundation of DDD is discovering and using shared terminology through conversations with domain experts. This common vocabulary ensures code reflects real-world business processes rather than arbitrary technical abstractions.
Principles:
For Norwegian admission system:
Opptakskrav, Karakterpoeng, KvoteAdmissionRequirement, GradePoints, QuotaRule, Data, Manager, Processor, HandlerSeparate domain models into distinct boundaries based on different meanings of the same terms.
Example for admission system:
Each context has its own model, even if terms overlap.
Define relationships between bounded contexts:
Objects defined by identity, not attributes.
Characteristics:
class Student:
"""Entity: Student identity matters, attributes can change."""
def __init__(self, student_id: StudentId, name: str):
self._id = student_id # Identity
self._name = name # Can change
self._grades: List[Grade] = []
@property
def id(self) -> StudentId:
return self._id
def add_grade(self, grade: Grade) -> None:
self._grades.append(grade)
Objects defined by attributes, not identity.
Characteristics:
@dataclass(frozen=True)
class Grade:
"""Value Object: Two grades with same values are identical."""
subject: str
score: int
def __post_init__(self):
if not 1 <= self.score <= 6:
raise ValueError("Grade must be between 1 and 6")
@dataclass(frozen=True)
class CompetencePoints:
"""Value Object: Immutable, defined by value."""
value: Decimal
def add(self, other: 'CompetencePoints') -> 'CompetencePoints':
return CompetencePoints(self.value + other.value)
Cluster of entities and value objects with defined boundaries. Aggregates are crucial for maintaining consistency and controlling access to the domain model.
Rules:
class AdmissionApplication:
"""Aggregate Root: Controls access to internal entities."""
def __init__(self, application_id: ApplicationId, student: Student):
self._id = application_id
self._student = student
self._program_choices: List[ProgramChoice] = []
self._status = ApplicationStatus.DRAFT
def add_program_choice(self, program: Program, priority: int) -> None:
"""Root controls modification of internal entities."""
if len(self._program_choices) >= 10:
raise DomainError("Maximum 10 program choices allowed")
choice = ProgramChoice(program, priority)
self._program_choices.append(choice)
def submit(self) -> None:
"""Root enforces invariants."""
if not self._program_choices:
raise DomainError("Cannot submit without program choices")
self._status = ApplicationStatus.SUBMITTED
Stateless operations that handle domain logic which doesn't naturally belong to any single entity or value object. Domain services often orchestrate multiple aggregates.
Use when:
Avoid when:
class AdmissionEvaluationService:
"""Domain Service: Evaluates admission across multiple entities."""
def evaluate_application(
self,
application: AdmissionApplication,
rules: List[AdmissionRule]
) -> EvaluationResult:
"""Service coordinates between multiple domain objects."""
results = []
for rule in rules:
result = rule.evaluate(application.student)
results.append(result)
return EvaluationResult.from_rule_results(results)
Objects representing significant business occurrences that domain experts care about. Domain events decouple and coordinate complex workflows across subdomains.
Characteristics:
Benefits:
@dataclass(frozen=True)
class StudentAdmitted:
"""Domain Event: Something significant happened."""
student_id: StudentId
program_id: ProgramId
admitted_at: datetime
admission_basis: str
@dataclass(frozen=True)
class QuotaFilled:
"""Domain Event: Quota reached capacity."""
quota_id: QuotaId
filled_at: datetime
capacity: int
Data access abstractions that provide the illusion of an in-memory collection of aggregates. Repositories enable persistence ignorance, allowing you to switch storage technologies without affecting domain logic.
Responsibilities:
Key Benefits:
class AdmissionRuleRepository(Protocol):
"""Repository interface in domain layer."""
def find_by_program(self, program_id: ProgramId) -> List[AdmissionRule]:
"""Find all rules for a program."""
...
def find_by_id(self, rule_id: RuleId) -> Optional[AdmissionRule]:
"""Find specific rule."""
...
def save(self, rule: AdmissionRule) -> None:
"""Persist rule."""
...
Encapsulate complex object creation.
class AdmissionRuleFactory:
"""Factory: Creates complex admission rules."""
@staticmethod
def create_minimum_grade_rule(
subject: str,
minimum_grade: int
) -> MinimumGradeRule:
"""Create validated rule."""
if not 1 <= minimum_grade <= 6:
raise ValueError("Invalid grade")
return MinimumGradeRule(subject, minimum_grade)
@staticmethod
def create_from_config(config: dict) -> AdmissionRule:
"""Create rule from configuration."""
rule_type = config['type']
if rule_type == 'minimum_grade':
return MinimumGradeRule(config['subject'], config['grade'])
elif rule_type == 'quota':
return QuotaRule(config['quota_name'], config['capacity'])
# ... more types
Encapsulate business rules that can be combined.
class AdmissionSpecification(ABC):
"""Specification: Reusable business rule."""
@abstractmethod
def is_satisfied_by(self, student: Student) -> bool:
pass
def and_(self, other: 'AdmissionSpecification') -> 'AdmissionSpecification':
return AndSpecification(self, other)
class MinimumGradeSpecification(AdmissionSpecification):
def __init__(self, subject: str, minimum: int):
self._subject = subject
self._minimum = minimum
def is_satisfied_by(self, student: Student) -> bool:
grade = student.get_grade(self._subject)
return grade is not None and grade.score >= self._minimum
Encapsulate complex business rules and decisions.
class QuotaAssignmentPolicy:
"""Policy: Encapsulates quota assignment logic."""
def assign_quota(
self,
student: Student,
program: Program
) -> Optional[Quota]:
"""Determine which quota the student qualifies for."""
if student.has_special_competence():
return program.get_quota('special_competence')
elif student.is_first_time_applicant():
return program.get_quota('ordinary')
else:
return program.get_quota('supplementary')
Business rules that must always be true.
class Quota:
"""Entity with invariant: filled <= capacity."""
def __init__(self, name: str, capacity: int):
if capacity < 0:
raise ValueError("Capacity cannot be negative")
self._name = name
self._capacity = capacity
self._filled = 0
def fill_spot(self) -> None:
"""Invariant protected: cannot overfill."""
if self._filled >= self._capacity:
raise DomainError(f"Quota {self._name} is full")
self._filled += 1
@property
def available_spots(self) -> int:
"""Derived value from invariant."""
return self._capacity - self._filled
A rich domain model encapsulates business rules and logic within cohesive objects, protecting business concerns from infrastructure details. An anemic domain model separates data from behavior, resulting in procedural code disguised as objects.
# Just data, no behavior - violates OOP principles
class Student:
def __init__(self):
self.name = ""
self.grades = []
# Logic scattered in services
def calculate_points(student):
total = 0
for grade in student.grades:
total += grade.score * 4
return total
Problems with Anemic Models:
# Data + behavior together - proper encapsulation
class Student:
def __init__(self, name: str):
self._name = name
self._grades: List[Grade] = []
def add_grade(self, grade: Grade) -> None:
"""Domain logic with the data."""
if grade in self._grades:
raise DomainError("Grade already exists")
self._grades.append(grade)
def calculate_competence_points(self) -> CompetencePoints:
"""Behavior lives with data."""
total = sum(grade.to_points() for grade in self._grades)
return CompetencePoints(total)
Benefits of Rich Models:
The Central Problem: As codebases grow, cognitive load increases. Understanding how changes impact the system becomes difficult.
DDD's Solution: Create clear mental models that:
Key Insight: Without DDD structure, complexity overwhelms developers as systems scale. With DDD, the architecture provides a mental framework that remains comprehensible even as features multiply.
Student (identity: student number)Program (identity: program code)AdmissionRule (identity: rule ID)Grade(subject, score)CompetencePoints(value)QuotaName(name)StudentId(value)AdmissionApplication (root) containing ProgramChoice entitiesProgram (root) containing Quota entitiesAdmissionEvaluationServiceCompetencePointsCalculationServiceStudentAdmittedStudentRejectedQuotaFilledApplicationSubmittedAdmissionRuleRepositoryStudentRepositoryProgramRepositoryBefore adopting DDD patterns, developers should understand:
Start Simple: Don't apply all DDD patterns immediately. Begin with:
Progressive Enhancement:
Avoid Over-Engineering:
When applying DDD: