# 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.