DDD Entity Skill
Placement: everything here lives in the Domain layer. The driven ports an aggregate needs (repositories) are defined with [[hexagonal-port]]; orchestration around it belongs to an Application Service ([[cqrs-command]]). The layer rules come from [[clean-arch-layer]].
When to use
Creating a new aggregate, adding a value object, encoding an invariant/business rule, replacing primitives (string/int) with domain types, or emitting domain events.
Design process
- Find the aggregate boundary — what must stay transactionally consistent together?
- Pick the aggregate root — the only entry point; external code touches the aggregate only through it.
- Extract value objects — replace primitives that carry rules (Email, Money, DateRange).
- Encode invariants — make invalid state unrepresentable; enforce at construction and on every mutation.
- Provide factory methods — named constructors that return a valid aggregate or an error; no half-built objects.
- Define domain events if other parts of the system react to the change.
Core rules (normative)
Value Objects
- Immutable: no setters; "changing" returns a new instance.
- Equality by value, not identity.
- Self-validating: the constructor rejects invalid input — an existing VO is always valid.
- No identity, no lifecycle.
Entities & Aggregate Root
- Identity-based equality (stable ID across state changes).
- All external access goes through the root; internal members are not exposed mutable.
- The aggregate is the transactional consistency boundary — keep it small.
- Reference other aggregates by ID only, never by direct object pointer.
Invariants
- Enforced inside the domain, never in the application service or handler.
- A mutation that would break an invariant must fail (return error / throw domain error), not silently correct.
Anti-patterns to avoid
- Anemic model: entity = bag of public getters/setters, logic elsewhere.
- Primitive obsession:
string email, int cents instead of Email, Money.
- God aggregate: spanning many transactional boundaries; split it.
- Leaking domain logic into application/infrastructure layers.
- Injecting repositories/HTTP/DB into entities (the Domain imports nothing — see [[clean-arch-layer]]).
Validation checklist
Examples
Value object + aggregate root with invariants and factory, in Go and TypeScript, with an invariant-failure test: examples.md.
Reference
Deeper guidance (entity vs VO decision, immutability per language, domain events, aggregate sizing): reference.md.
1---2name: ddd-entity3description: DDD Entity Skill4---5# DDD Entity Skill67> **Placement:** everything here lives in the **Domain layer**. The driven ports an aggregate needs (repositories) are defined with [[hexagonal-port]]; orchestration around it belongs to an Application Service ([[cqrs-command]]). The layer rules come from [[clean-arch-layer]].89## When to use1011Creating a new aggregate, adding a value object, encoding an invariant/business rule, replacing primitives (string/int) with domain types, or emitting domain events.1213## Design process14151. **Find the aggregate boundary** — what must stay transactionally consistent together?162. **Pick the aggregate root** — the only entry point; external code touches the aggregate only through it.173. **Extract value objects** — replace primitives that carry rules (Email, Money, DateRange).184. **Encode invariants** — make invalid state unrepresentable; enforce at construction and on every mutation.195. **Provide factory methods** — named constructors that return a _valid_ aggregate or an error; no half-built objects.206. **Define domain events** if other parts of the system react to the change.2122## Core rules (normative)2324**Value Objects**2526- Immutable: no setters; "changing" returns a new instance.27- Equality **by value**, not identity.28- **Self-validating**: the constructor rejects invalid input — an existing VO is always valid.29- No identity, no lifecycle.3031**Entities & Aggregate Root**3233- Identity-based equality (stable ID across state changes).34- All external access goes **through the root**; internal members are not exposed mutable.35- The aggregate is the **transactional consistency boundary** — keep it small.36- Reference **other aggregates by ID only**, never by direct object pointer.3738**Invariants**3940- Enforced inside the domain, never in the application service or handler.41- A mutation that would break an invariant must fail (return error / throw domain error), not silently correct.4243## Anti-patterns to avoid4445- **Anemic model**: entity = bag of public getters/setters, logic elsewhere.46- **Primitive obsession**: `string email`, `int cents` instead of `Email`, `Money`.47- **God aggregate**: spanning many transactional boundaries; split it.48- **Leaking domain logic** into application/infrastructure layers.49- Injecting repositories/HTTP/DB into entities (the Domain imports nothing — see [[clean-arch-layer]]).5051## Validation checklist5253- [ ] Every primitive carrying a rule is a value object.54- [ ] Invalid construction is impossible (factory returns error / throws).55- [ ] All mutation paths re-check invariants.56- [ ] Aggregate references others by ID only.57- [ ] Domain package has zero infrastructure imports.58- [ ] Invariants are covered by unit tests asserting the _failure_ path.5960## Examples6162Value object + aggregate root with invariants and factory, in Go and TypeScript, with an invariant-failure test: **[examples.md](examples.md)**.6364## Reference6566Deeper guidance (entity vs VO decision, immutability per language, domain events, aggregate sizing): **[reference.md](reference.md)**.