Appearance
ADR-008: DDD Base Classes
Status: Accepted Date: 2026-02-07
Context
The domain model has entities with varying levels of importance and lifecycle ownership. Some entities are independent (Project, Character), while others only exist within the context of a parent (Section within TimelineUpdate, FactionMembership within Faction). The team follows Domain-Driven Design patterns.
Decision
Use DDD base classes: AggregateRoot, Entity, and Value Objects.
Entity Hierarchy
Entity (base class)
All domain objects with identity inherit from Entity.
| Field | Type | Notes |
|---|---|---|
| Id | Guid (UUIDv7) | Internal only |
| CreatedAt | DateTime | Auto-set via DbContext SaveChanges override |
| CreatedBy | Guid | Auto-set from current user context |
| UpdatedAt | DateTime | Auto-set on insert and update |
| UpdatedBy | Guid | Auto-set from current user context |
AggregateRoot : Entity
Top-level domain objects that define transactional boundaries.
| Field | Type | Notes |
|---|---|---|
| SequenceNumber | int | Project-scoped public ID |
Additionally supports domain events collection for raising events on state changes.
Value Objects
Objects with no identity, compared by their properties. Immutable.
Classification
| Type | Entities |
|---|---|
| Aggregate Roots | Project, Character, Faction, TimelineUpdate |
| Entities | Section, Item, ProjectMember, FactionMembership, Relationship, FactionRelationship, ChangelogEntry |
| Value Objects | Theme (primaryColor + accentColor), RelationshipType, DevelopmentStatus |
Rationale
- Aggregate roots define consistency boundaries - all changes to child entities go through the aggregate root
- Only aggregate roots get public sequence numbers - child entities are accessed through their parent and don't need public-facing identifiers
- Audit fields on all entities via the base Entity class, auto-populated by the DbContext so developers never need to set them manually
- Protected setters enforce that state changes go through domain methods, not direct property assignment
Consequences
- EF Core entity configurations need to handle the inheritance hierarchy
- SaveChangesAsync override in DbContext auto-populates CreatedAt/CreatedBy/UpdatedAt/UpdatedBy
- Domain events on AggregateRoot can trigger side effects (activity feed entries, notifications) through MediatR or similar
- Value Objects configured as owned types in EF Core