36 lines
2.4 KiB
Markdown
36 lines
2.4 KiB
Markdown
# Entities, Value Objects, and Aggregates
|
|
|
|
Start from identity and business invariants rather than database tables or a target class count.
|
|
|
|
## Identity and value
|
|
|
|
An entity retains identity through changes; a value object is interchangeable with another value having the same attributes. Choose equality and mutability accordingly. Use existing identifier conventions rather than adding a new ID scheme by default.
|
|
|
|
Value objects should enforce their actual invariants and support immutable transformations. Do not infer business rules from examples: negative money may be valid for one concept and invalid for another.
|
|
|
|
## Aggregate ownership
|
|
|
|
An aggregate owns a cohesive set of state and invariants, with a root controlling its changes. Keep it small enough to load and update for its real use cases; use references by ID for independent aggregates where the project's model calls for them.
|
|
|
|
Ask:
|
|
|
|
- Which rules must hold after every successful operation?
|
|
- Who owns each lifecycle and may change its state?
|
|
- Which state must be read and written together?
|
|
- What prevents two concurrent commands from both accepting incompatible outcomes?
|
|
- Can a partial result or delayed update be valid?
|
|
|
|
Do not embed a growing independent lifecycle merely to make all relationships reachable through one object.
|
|
|
|
## Atomicity and concurrency
|
|
|
|
Aggregate boundaries and transaction boundaries are related, not identical. When an operation must commit or roll back across multiple aggregate documents, use the supported service-layer transaction required by the domain.
|
|
|
|
In this repository, [ADR-0011](../../../../docs/adr/0011-rental-aggregate-boundaries.md) explicitly requires Mongo transactions for relevant rental operations. Preserve optimistic version checks, occupancy contention, and uniqueness constraints; replacing them with asynchronous events or a query-only overlap check would weaken the invariant.
|
|
|
|
Use eventual consistency only when delayed or partial progress is acceptable, with explicit failure and recovery handling. Keep remote calls and long-running work outside database transactions.
|
|
|
|
## Verify the boundary
|
|
|
|
Exercise the actual operation, including invalid transitions, stale versions, duplicate commands, and competing callers where relevant. Prove the invariant at the storage/integration boundary when an in-memory test cannot establish it. Preserve distinct existing test coverage when moving behavior behind a new interface.
|