Domain-Driven Design
Value Objects
Values defined by what they are, not by who they are.
A value object is defined by its data. Two instances with the same components represent the same value. When the value changes, replace it. There is no separate identity whose history the model follows.
MiniVerine’s SentAt is an instant and MessageType is a wire name. Two envelopes may contain equal instances of either value. That does not make them the same envelope.
Same data, same thing
MiniVerine uses C# records for value equality:
public record SentAt(DateTimeOffset Value);
public record MessageType(string Value);
public record Attempts(int Value);
An attempt count is replaced rather than given a life of its own:
var first = new Attempts(1);
var second = first with { Value = 2 };
Assert.Equal(new Attempts(2), second);
Assert.Equal(new Attempts(1), first);
The number changed for the envelope. The value Attempts(1) did not mutate into Attempts(2).
A wrapper is not enough
Record syntax supplies structural equality. It does not make the data valid. Attempts(0) still compiles, so MiniVerine pairs the record with a validator requiring at least one. MessageType("") also compiles and fails validation later.
MiniVerine deliberately keeps construction cheap and defines validation separately. Its tests apply those validators today. Running them at the edge of the bus is still planned. Another model might protect the invariant in a factory or constructor. Either way, the invariant belongs to the value.
Wrapping also prevents two equal primitives from pretending to mean the same thing. MessageType("application/json") and ContentType("application/json") contain equal strings but answer different questions. A method expecting MessageType cannot accidentally receive ContentType.
The id is a value; the envelope is not
EnvelopeId is also a record of a Guid:
public record EnvelopeId(Guid Value);
The wrapper is a value that carries an entity’s identity. Two equal EnvelopeId instances mean “the same envelope id”; they are not envelopes themselves. Domain code should prevent two distinct envelopes claiming that id, but the value object can be copied freely.
This is the distinction from an entity: values compare by their components; entities use an id to preserve continuity while their components change.
Once entities and values sit inside a consistency boundary, the application needs to retrieve that aggregate root without speaking in tables. That is Repositories.