PMQ.Domain
902 downloads · the most recent of the suite · repository
The DDD building blocks: entity, aggregate root, value object and domain event.
The problem
Every project that does DDD rewrites the same four base classes, and each one gets a different detail wrong: an entity compared by reference instead of by identity, a mutable value object, an event published before persistence confirms.
The decisions, in the code
Entity inherits from Validatable. A broken invariant becomes a notification accumulated on the
entity itself, never an exception, and that is PMQ.Notifications coming in as the base of the
domain, and not as a layer on top of it. The reasoning is written in the class:
/// Inherits Validatable: a broken invariant becomes a notification accumulated on the entity,
/// never an exception. Exceptions are for programming errors — a violated business rule is an
/// expected outcome, and unwinding the stack for it both costs more and reports only the first
/// problem instead of all of them.
public abstract class Entity<TId> : Validatable, IHasDomainEvents, IEquatable<Entity<TId>>
where TId : notnull
Equality by identity, said out loud. Two instances with the same Id are the same entity even
if everything else differs. It is the DDD definition and it is where people get it wrong the most,
so it is in the class documentation along with the pointer: for value semantics, use ValueObject
or record.
Parameterless constructor, explained. It exists because the ORM needs it, and it is annotated saying so: without the note, the next person to read it removes it thinking it is a leftover.
/// <summary>Parameterless constructor required by ORMs such as Entity Framework Core.</summary>
protected Entity() { }
Events accumulated, not published. The entity keeps the events in a private list and exposes
IReadOnlyCollection; the application layer dispatches them, after the transaction confirms.
Publishing at the moment the event is born delivers the side effect of an operation that can still
fail.
The size is the proposal
Six files, 533 lines counting documentation. There is no generic repository, no unit of work, no event bus. A library of domain blocks that brings infrastructure along forces the consumer to accept persistence decisions that are not its own.