yonatangross/orchestkit

domain-driven-design

DDD tactical patterns for complex business modeling including entities, value objects, aggregates, domain services, repositories, specifications, and bounded contexts.

Quelltext ansehen
Originales Skill-Dokument

Aus dem Quell-Repository gerendert; Überschriften, Beispiele, Code, Tabellen, Links und Bilder bleiben erhalten.

Domain-Driven Design Tactical Patterns

Model complex business domains with entities, value objects, and bounded contexts.

Overview

  • Modeling complex business logic
  • Separating domain from infrastructure
  • Establishing clear boundaries between subdomains
  • Building rich domain models with behavior
  • Implementing ubiquitous language in code

Building Blocks Overview

┌─────────────────────────────────────────────────────────────┐
│                    DDD Building Blocks                       │
├─────────────────────────────────────────────────────────────┤
│  ENTITIES           VALUE OBJECTS        AGGREGATES         │
│  Order (has ID)     Money (no ID)        [Order]→Items      │
│                                                              │
│  DOMAIN SERVICES    REPOSITORIES         DOMAIN EVENTS      │
│  PricingService     IOrderRepository     OrderSubmitted     │
│                                                              │
│  FACTORIES          SPECIFICATIONS       MODULES            │
│  OrderFactory       OverdueOrderSpec     orders/, payments/ │
└─────────────────────────────────────────────────────────────┘

Quick Reference

Entity (Has Identity)

python
from dataclasses import dataclass, field
from uuid import UUID
from uuid_utils import uuid7

@dataclass
class Order:
    """Entity: Has identity, mutable state, lifecycle."""
    id: UUID = field(default_factory=uuid7)
    customer_id: UUID = field(default=None)
    status: str = "draft"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Order):
            return NotImplemented
        return self.id == other.id  # Identity equality

    def __hash__(self) -> int:
        return hash(self.id)

ID generation is a house rule, not a taste call: Read("references/ork-delta.md").

Value Object (Immutable)

python
from dataclasses import dataclass
from decimal import Decimal

@dataclass(frozen=True)  # MUST be frozen!
class Money:
    """Value Object: Defined by attributes, not identity."""
    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)

Canonical Address / DateRange boilerplate is not restated here. See the upstream coverage table below.

Key Decisions

DecisionRecommendation
Entity vs VOHas unique ID + lifecycle? Entity. Otherwise VO
Entity equalityBy ID, not attributes
Value object mutabilityAlways immutable (frozen=True)
Repository scopeOne per aggregate root
Domain eventsCollect in entity, publish after persist
Context boundariesBy business capability, not technical

Rules Quick Reference

RuleImpactWhat It Covers
aggregate-boundaries (load rules/aggregate-boundaries.md)HIGHAggregate root design, reference by ID, one-per-transaction
aggregate-invariants (load rules/aggregate-invariants.md)HIGHBusiness rule enforcement, specification pattern
aggregate-sizing (load rules/aggregate-sizing.md)HIGHRight-sizing, when to split, eventual consistency

When NOT to Use

Under 5 entities? Skip DDD entirely. The ceremony costs more than the benefit.

PatternInterviewHackathonMVPGrowthEnterpriseSimpler Alternative
AggregatesOVERKILLOVERKILLOVERKILLSELECTIVEAPPROPRIATEPlain dataclasses with validation
Bounded contextsOVERKILLOVERKILLOVERKILLBORDERLINEAPPROPRIATEPython packages with clear imports
CQRSOVERKILLOVERKILLOVERKILLOVERKILLWHEN JUSTIFIEDSingle model for read/write
Value objectsOVERKILLOVERKILLBORDERLINEAPPROPRIATEREQUIREDTyped fields on the entity
Domain eventsOVERKILLOVERKILLOVERKILLSELECTIVEAPPROPRIATEDirect method calls between services
Repository patternOVERKILLOVERKILLBORDERLINEAPPROPRIATEREQUIREDDirect ORM queries in service layer

Rule of thumb: DDD adds ~40% code overhead. Only worth it when domain complexity genuinely demands it (5+ entities with invariants spanning multiple objects). A CRUD app with DDD is a red flag.

Anti-Patterns (FORBIDDEN)

python
# NEVER have anemic domain models (data-only classes)
@dataclass
class Order:
    id: UUID
    items: list  # WRONG - no behavior!

# NEVER leak infrastructure into domain
class Order:
    def save(self, session: Session):  # WRONG - knows about DB!

# NEVER use mutable value objects
@dataclass  # WRONG - missing frozen=True
class Money:
    amount: Decimal

# NEVER have repositories return ORM models
async def get(self, id: UUID) -> OrderModel:  # WRONG - return domain!

Upstream coverage (do not restate)

These topics are documented first-party. Read the source instead of re-deriving them here; only the house consequences are kept, in references/ork-delta.md.

TopicSource
Entity / value-object dataclass mechanics: frozen, __post_init__, inherited field ordering, kw_onlyhttps://docs.python.org/3/library/dataclasses.html
Domain event definition, deferred dispatch, handler wiring, dispatch before vs after commithttps://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation
Bounded contexts, context map, ubiquitous language, integration patterns (shared kernel, customer-supplier, conformist, open host service, published language)https://learn.microsoft.com/en-us/azure/architecture/microservices/model/domain-analysis
Anti-corruption layer: what it translates and what it costshttps://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer
UUIDv7 generation, server side and in Pythonhttps://www.postgresql.org/docs/18/functions-uuid.html and https://github.com/aminalaee/uuid-utils
Payment amounts in minor units, zero-decimal currencieshttps://docs.stripe.com/currencies
Publishing events to a Redis Stream (XADD field maps, pipelining)https://redis.io/docs/latest/commands/xadd/
Layered architecture enforcement, project-structure validation, test standardsthe architecture-patterns skill in this plugin

Two subjects deliberately stay in this skill rather than routing upstream: the repository and Unit of Work implementation, which lives in full in references/repositories.md, and aggregate boundaries, invariants, and sizing, which live in full in rules/.

Related Skills

  • rules/aggregate-boundaries.md, rules/aggregate-invariants.md, rules/aggregate-sizing.md - aggregate design, in this skill
  • ork:architecture-patterns - Layer boundaries and project structure validation
  • ork:distributed-systems - Cross-aggregate coordination
  • ork:database-patterns - Schema design for DDD

References

Load on demand with Read("references/<file>"):

FileContent
ork-delta.mdHouse rules: UUIDv7, event drain ordering, ACL boundary, source layout
repositories.mdRepository pattern, Unit of Work, SQLAlchemy mapping

Capability Details

entities

Keywords: entity, identity, lifecycle, mutable, domain object Solves: Identity equality by ID, and the house UUIDv7 ID rule in references/ork-delta.md. Dataclass mechanics route upstream.

value-objects

Keywords: value object, immutable, frozen, dataclass, structural equality Solves: When to use VO vs entity. frozen=True semantics and inherited field ordering route upstream.

domain-services

Keywords: domain service, business logic, cross-aggregate, stateless Solves: When to use domain service, logic spanning aggregates

repositories

Keywords: repository, persistence, collection, IRepository, protocol Solves: Implement repository pattern, abstract DB access, ORM mapping

bounded-contexts

Keywords: bounded context, context map, ACL, subdomain, ubiquitous language Solves: The house ACL boundary and context-first source layout in references/ork-delta.md. Context mapping and integration patterns route upstream.

aus demselben Repository

Weitere Skills

Alle Skills