What are Entities and Aggregates?
Entities and Aggregates are domain objects defined by their identity, not by their attributes. Two objects with the same attributes but different IDs are considered distinct objects. The main difference lies in their architectural role, not in their change tracking implementation:- Entity: Domain object with identity that normally lives inside an Aggregate
- Aggregate: Root entity that defines a consistency boundary and is the entry point for repositories and domain events
Entity
Objects with identity that live inside Aggregates. Not accessed directly through repositories.
Aggregate
Root of a cluster of related entities. Models the consistency boundary persisted by a repository.
Key Differences
Entity and Aggregate both inherit change tracking from BaseEntity.
Therefore, either one can technically contain and track nested entities.
The distinction is architectural: repositories in the core API operate on
aggregate roots, and only Aggregate provides domain-event management.Creating Entities
Entities are identity-bearing domain objects, typically used as part of an Aggregate. They have the same validation, serialization, and change tracking capabilities inherited fromBaseEntity, but they do not manage domain events:
Creating Aggregates
Aggregates are the root of a cluster of entities and define consistency boundaries:Identity & Equality
Both Entities and Aggregates are compared by ID, not by attributes:Validation
Both support validation with Standard Schema-compatible validators such as Zod:Optional Input Properties
Sometimes you need properties that are required in the entity but optional when constructing it, because they’re generated internally via hooks. Use the second generic parameter to specify optional input fields:Fields marked as optional input are still required in the entity and validated by the schema. The difference is they’re optional when calling
new User() because they’ll be generated in onBeforeCreate.- Auto-generated passwords or tokens
- Timestamps (createdAt, updatedAt)
- Computed identifiers
- Default configurations
Consistency Boundaries
Immediate Consistency
Model aggregate methods so their invariants are valid before persistence.
Eventual Consistency
Coordinate changes across aggregate boundaries with domain events or application services.
Change Tracking
Both entities and aggregates track changes to their own properties and nestedBaseEntity instances. In normal DDD usage, call getChanges() on the aggregate
root so the result represents the persistence boundary:
Construction establishes the initial tracking baseline.
getChanges() reports
mutations made after construction or after markAsClean(); it does not emit a
create operation for the root object itself. Repositories use isNew() to
decide whether the aggregate root requires an INSERT.