Domain-Driven Design (DDD)
debt(d7/e7/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints note that phpstan is listed but automated detection is explicitly 'no'. The code pattern (classes named with technical terms instead of domain language; anemic domain model) requires human judgment and careful code review to identify — no tool can reliably detect that your naming doesn't align with business vocabulary or that you've missed bounded contexts.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix says to start with ubiquitous language — renaming classes and methods to match business vocabulary. But the common_mistakes show systemic issues: wrong bounded contexts, missing domain expert collaboration, patterns applied without domain understanding. Correcting a DDD misapplication (e.g., one giant bounded context, anemic models throughout) touches multiple layers and files across the entire codebase, well beyond a single-component fix.
Closest to 'strong gravitational pull' (e7, scored b7). DDD applies to web, cli, and queue-worker contexts — it shapes every layer of the application. The choice of bounded contexts, aggregates, and ubiquitous language defines how every future feature is modelled and where code lives. Every change is shaped by the domain model structure, and a poorly applied DDD (e.g., one bounded context for everything) imposes a persistent productivity tax on all work streams.
Closest to 'serious trap' (t7). The canonical misconception is explicit: developers believe DDD means using repositories, aggregates, and value objects everywhere, when DDD is primarily about aligning software with business language and boundaries. This directly contradicts how most developers encounter DDD (through its tactical patterns in blog posts and frameworks), causing them to over-engineer CRUD with patterns while missing the actual point. The common mistakes confirm this is a widely recurring, costly misunderstanding.
Also Known As
TL;DR
Explanation
DDD (Eric Evans, 2003) advocates building a rich domain model using the Ubiquitous Language — a shared vocabulary between developers and domain experts. Key building blocks: Entities (objects with identity), Value Objects (immutable, equality by value), Aggregates (consistency boundaries), Repositories (data access abstraction), Domain Services (stateless domain logic), Domain Events, and Bounded Contexts (explicit boundaries between subdomains). In PHP, DDD is applied by separating the domain layer from infrastructure, avoiding anemic domain models, and letting domain experts drive the naming and structure.
Diagram
flowchart TD
subgraph STRAT[Strategic Design]
BC[Bounded Contexts]
CM[Context Map]
UL[Ubiquitous Language]
BC --- CM --- UL
end
subgraph TACT[Tactical Design]
AGG[Aggregates]
ENT[Entities]
VO[Value Objects]
DOM[Domain Events]
REPO[Repositories]
AGG --> ENT & VO
AGG --> DOM
REPO -->|persists| AGG
end
STRAT -->|shapes| TACT
style AGG fill:#6e40c9,color:#fff
style VO fill:#238636,color:#fff
style DOM fill:#d29922,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Applying DDD to simple CRUD applications — the overhead of aggregates, repositories, and domain events is rarely justified.
- Not developing a ubiquitous language with domain experts — the code uses different words than the business, causing miscommunication.
- Starting with the technical patterns (aggregates, events) without first understanding the domain — patterns without domain knowledge produce over-engineered CRUD.
- One bounded context for the entire application — contexts that are too large defeat the purpose.
Code Examples
// Database-shaped domain model — not DDD:
class UserRow {
public int $id;
public string $email;
public int $status_id; // Foreign key — not domain language
public ?int $billing_address_id;
// Shaped by the database, not the domain
}
// DDD building blocks in PHP
// Entity — has identity, mutable state
class Order {
public function __construct(private OrderId \$id) {}
public function getId(): OrderId { return \$this->id; }
}
// Value Object — no identity, immutable, compared by value
readonly class Money {
public function __construct(
public int \$amount,
public string \$currency,
) {}
public function equals(Money \$other): bool {
return \$this->amount === \$other->amount && \$this->currency === \$other->currency;
}
}
// Domain Service — logic that doesn't naturally fit an entity
class PricingService {
public function calculateTotal(Order \$order, TaxRate \$rate): Money {}
}
// Repository — collection abstraction over persistence
interface OrderRepository {
public function find(OrderId \$id): ?Order;
public function save(Order \$order): void;
}