DDD Code Review
Overview
Validates code against Domain-Driven Design principles. Use alongside ddd-workflow:code-review-oop for complete review:
- code-review-oop: SRP, composition, design patterns, dependency injection
- code-review-ddd: Domain modeling, bounded contexts, tactical patterns
Core principle: Domain layer expresses business concepts in pure Python. Infrastructure concerns stay out. Conditionals in entity methods are a smell — push to composed objects.
Quick DDD Compliance Check
Layered Architecture
- Domain layer has zero external imports (no SQLAlchemy, requests, etc.)
- Application layer orchestrates; no business logic
- Infrastructure implements domain interfaces
- Dependencies point inward: infrastructure → application → domain
Value Objects
- Immutable (
@dataclass(frozen=True)) - Equality by value, not identity
- Validation in
__post_init__ - No side effects
Entities
- Identity-based equality
- Factory methods for creation (
Entity.create(...)) - State transitions via State pattern (no conditionals)
- Emits domain events for significant changes
Aggregates
- Single entry point (aggregate root)
- Protects invariants
- Transactional consistency boundary
- External references by ID only
Domain Events
- Past tense naming (
OrderPlaced, notPlaceOrder) - Immutable (frozen dataclass)
- Contains all data handlers need
- Emitted by entities, not services
Repositories
- Interface defined in domain layer
- Works with domain models only (not ORM)
- Implementation in infrastructure layer
- Specific query methods (not generic
search())
Bounded Contexts
- Clear boundaries between contexts
- Shared kernel for cross-context models
- Anti-corruption layer for external systems
Ubiquitous Language
- Code uses domain terminology
- No technical jargon in domain layer
- Names match domain expert vocabulary
Red Flags
| Red Flag | Problem | Fix |
|---|---|---|
from sqlalchemy in domain | Infrastructure leak | Protocol in domain, impl in infrastructure |
| Mutable value object | Identity confusion | frozen=True dataclass |
if in entity method | Conditional smell | State pattern, composed objects |
| Service modifies entity | Anemic model | Entity coordinates via composition |
repository.search(**kwargs) | Generic query | Specific query methods |
| Aggregate exposes mutable list | Broken invariants | Return immutable views |
| Event created in service | Split behavior | Entity emits events |
| Import from other context | Tight coupling | ACL or shared kernel |
process, handle, do names | Unclear meaning | Ubiquitous language |
Quick Self-Review Questions
- Dependency Direction: Does domain import from infrastructure? → Move interface to domain
- Value Object: Same values = equal? → Make immutable, value-based equality
- Entity Behavior: Conditionals in entity? → Push to State pattern
- Aggregate Invariant: Can external code break it? → Encapsulate
- Domain Event: Past tense with all needed data? → Rename, include data
- Repository: Exposes ORM or generic queries? → Domain models, specific methods
- Bounded Context: Direct imports from other context? → Add ACL
- Language: Would domain expert understand? → Use ubiquitous language
Integration with code-review-oop
| Concern | Skill |
|---|---|
| Single Responsibility | code-review-oop |
| Dependency Injection | code-review-oop |
| State/Strategy Patterns | code-review-oop |
| Eliminating Conditionals | code-review-oop |
| Domain Layer Purity | code-review-ddd |
| Value Objects & Entities | code-review-ddd |
| Aggregates & Events | code-review-ddd |
| Bounded Contexts | code-review-ddd |
References (load on demand)
- Detailed violation/fix examples:
references/violations-and-fixes.md - REST API architecture guide:
references/rest-api-guide.md
When to Skip
- CRUD-only code with no business logic
- Scripts or utilities
- Prototypes not going to production
- Pure infrastructure code
For domain model code: Use this checklist.
