Skip to main content

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.

インストールへ移動

ソース情報

リポジトリ
SmidigStorm/regelverk-python
ソースの最終更新活動
2025年10月28日 20:23
検出された SKILL.md の言語
英語
スター
0
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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.
# Domain-Driven Design (DDD) Skill You are assisting with code that must follow Domain-Driven Design principles. ## Core Concept **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**: - Complex business requirements that go beyond CRUD operations - Business logic that cannot be adequately expressed through simple data structures - Domains where standard architectural patterns feel insufficient - Systems requiring deep domain understanding and continuous evolution **When NOT to Use DDD**: - Simple CRUD applications - When business logic is minimal - Projects with tight deadlines and simple requirements - Team lacks OOP fundamentals and SOLID principles understanding ## Strategic Design ### Ubiquitous Language 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**: - Use the same terms in code as domain experts use - No translation layer between business and code - Class names, method names, variables match domain vocabulary - The language evolves as understanding deepens **For Norwegian admission system**: - Use Norwegian domain terms: `Opptakskrav`, `Karakterpoeng`, `Kvote` - Or agreed English equivalents: `AdmissionRequirement`, `GradePoints`, `Quota` - Avoid generic terms: `Rule`, `Data`, `Manager`, `Processor`, `Handler` ### Bounded Contexts Separate domain models into distinct boundaries based on different meanings of the same terms. **Example for admission system**: - **Admission Context**: Rules, evaluation, quotas - **Student Context**: Personal data, grades, applications - **Reporting Context**: Statistics, analytics, exports Each context has its own model, even if terms overlap. ### Context Mapping Define relationships between bounded contexts: - **Shared Kernel**: Common domain model - **Customer-Supplier**: One context depends on another - **Anti-Corruption Layer**: Translate between contexts - **Published Language**: Standard interchange format ## Tactical Design Patterns ### 1. Entities Objects defined by identity, not attributes. **Characteristics**: - Has unique identifier - Mutable - Identity persists through changes - Lifecycle matters ```python 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) ``` ### 2. Value Objects Objects defined by attributes, not identity. **Characteristics**: - No unique identifier - Immutable - Equality by value comparison - Can be shared ```python @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) ``` ### 3. Aggregates Cluster of entities and value objects with defined boundaries. Aggregates are crucial for maintaining consistency and controlling access to the domain model. **Rules**: - One entity is the Aggregate Root (the entry point) - External objects can only reference the root (never internal entities) - Root enforces all invariants across the aggregate - Transaction boundaries align with aggregates - Keep aggregates as small as possible for performance - External references point only to roots, preventing external manipulation of internal state ```python 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 ``` ### 4. Domain Services 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**: - Operation involves multiple domain objects - Operation is a significant domain concept in itself - Operation is stateless (no instance variables) - Forcing the behavior into an entity would feel unnatural **Avoid when**: - The behavior naturally belongs to a specific entity - It would create an anemic domain model by extracting entity behavior ```python 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) ``` ### 5. Domain Events Objects representing significant business occurrences that domain experts care about. Domain events decouple and coordinate complex workflows across subdomains. **Characteristics**: - Past tense naming (describes what happened) - Immutable (events cannot be changed) - Contains all relevant data for the event - Timestamped - Represent facts that have occurred in the domain **Benefits**: - Loose coupling between bounded contexts - Audit trail of domain changes - Enable event-driven architectures - Support eventual consistency patterns ```python @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 ``` ### 6. Repositories 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**: - Add/remove aggregates - Find aggregates by criteria - Reconstitute aggregates from storage - Work exclusively with aggregate roots, not individual entities **Key Benefits**: - Domain layer stays independent of infrastructure - Easier to test with in-memory implementations - Can swap persistence strategies (SQL, NoSQL, file system) transparently ```python 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.""" ... ``` ### 7. Factories Encapsulate complex object creation. ```python 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 ``` ## Domain Model Patterns ### Specification Pattern Encapsulate business rules that can be combined. ```python 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 ``` ### Policy Pattern Encapsulate complex business rules and decisions. ```python 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') ``` ## Protecting Invariants Business rules that must always be true. ```python 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 ``` ## Rich Domain Model vs Anemic Domain Model 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. ### Anemic (BAD - Avoid) ```python # 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**: - Business logic scattered across service classes - Data structures exposed and vulnerable to invalid states - Difficult to maintain consistency and enforce invariants - Loses benefits of encapsulation and OOP - Cognitive load increases as codebase grows ### Rich (GOOD - Prefer) ```python # 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**: - Encapsulation: Business logic isolated from infrastructure - Testability: Pure domain logic is easier to test - Maintainability: Clear mental models reduce cognitive load - Scalability: As complexity grows, structure prevents degradation ## Why DDD Matters: Cognitive Load and Mental 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: - Reduce cognitive load through well-defined boundaries - Improve as the system grows (rather than degrade) - Make the codebase easier to reason about over time - Enable faster onboarding and safer changes **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. ## Code Review Checklist - [ ] Does code use ubiquitous language from domain? - [ ] Are entities and value objects properly distinguished? - [ ] Are aggregates properly bounded? - [ ] Are invariants protected? - [ ] Is the domain model rich (not anemic)? - [ ] Are domain services used appropriately? - [ ] Are domain events captured for significant happenings? - [ ] Do repositories work with aggregate roots? - [ ] Are bounded contexts clearly separated? - [ ] Does the code reduce cognitive load through clear structure? - [ ] Will this design scale as complexity grows? ## Practical Application for Admission Rules ### Entities - `Student` (identity: student number) - `Program` (identity: program code) - `AdmissionRule` (identity: rule ID) ### Value Objects - `Grade(subject, score)` - `CompetencePoints(value)` - `QuotaName(name)` - `StudentId(value)` ### Aggregates - `AdmissionApplication` (root) containing `ProgramChoice` entities - `Program` (root) containing `Quota` entities ### Domain Services - `AdmissionEvaluationService` - `CompetencePointsCalculationService` ### Domain Events - `StudentAdmitted` - `StudentRejected` - `QuotaFilled` - `ApplicationSubmitted` ### Repositories - `AdmissionRuleRepository` - `StudentRepository` - `ProgramRepository` ## Prerequisites for DDD Success Before adopting DDD patterns, developers should understand: - Object-oriented programming fundamentals - SOLID principles (especially Single Responsibility and Dependency Inversion) - Design patterns (Strategy, Factory, Specification) - Separation of concerns - Interface-based design **Start Simple**: Don't apply all DDD patterns immediately. Begin with: 1. Ubiquitous language 2. Rich domain models (avoid anemic models) 3. Clear entity vs value object distinction 4. Then gradually adopt aggregates, domain events, specifications ## Managing DDD Complexity **Progressive Enhancement**: - Start with basic entities and value objects - Add aggregates when consistency boundaries become clear - Introduce domain events when decoupling is needed - Apply specifications when rule combinations emerge **Avoid Over-Engineering**: - Not every class needs to be an aggregate - Not every operation needs a domain service - Not every change needs a domain event - Keep it simple until complexity demands structure ## Response Format When applying DDD: 1. Identify domain concepts from requirements 2. Classify as entity, value object, aggregate, or service 3. Define ubiquitous language terms 4. Protect invariants within aggregates
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る