Modeling events well
Events are the foundation everything else is built on. Get them right and projections, reactors, and read models fall into place. Get them wrong and no amount of clever projection code will save you. This is the guide to getting them right.
The guiding principle behind all of it: an event is a fact — an immutable record of something that happened. Every rule below follows from taking that seriously.
Name events as facts, in the past tense
Section titled “Name events as facts, in the past tense”An event states what happened, so its name is a past-tense verb phrase in the language of the domain: OrderPlaced, AddressChanged, PaymentCaptured, BookReturned. Not CreateOrder (that’s a command — an intent), not OrderState (that’s a model). If you can’t name it in the past tense, it isn’t an event yet.
// ✅ A fact that happened[EventType]public record AddressChanged(Address Address);
// ❌ An intent (that's a command) or a state blob (that's a read model)[EventType]public record UpdateAddress(Address Address);One event, one purpose
Section titled “One event, one purpose”Each event captures a single, meaningful change. Resist the “kitchen-sink” event that carries everything about an entity with most fields irrelevant on any given change. Multipurpose events force every consumer to figure out which change actually happened.
// ❌ One event trying to be everything — consumers must guess what changed[EventType]public record CustomerUpdated(string? Name, Address? Address, Email? Email, bool? Deactivated);
// ✅ Distinct facts — each consumer subscribes to exactly what it cares about[EventType] public record CustomerRenamed(CustomerName Name);[EventType] public record AddressChanged(Address Address);[EventType] public record CustomerDeactivated(DeactivationReason Reason);Never nullable — if it’s optional, you need a second event
Section titled “Never nullable — if it’s optional, you need a second event”This is the rule that trips up newcomers most, and it’s the most important. An event records what was true at the moment it happened. A nullable property means “this fact sometimes didn’t happen” — which is a contradiction. If a value is sometimes present and sometimes not, that’s two different facts, so model two events.
// ❌ Nullable smell — "sometimes there's a discount, sometimes not"[EventType]public record OrderPlaced(OrderId Id, Money Total, Money? Discount);
// ✅ Two facts[EventType] public record OrderPlaced(OrderId Id, Money Total);[EventType] public record DiscountApplied(OrderId Id, Money Amount);Capture the decision, not the field write
Section titled “Capture the decision, not the field write”The temptation coming from CRUD is to mirror table columns: a Customer changed, so emit CustomerUpdated. But the value of event sourcing is in the meaning. AddressChanged tells you a customer moved; a generic update tells you nothing. Model the business decision or transition, and the audit trail, analytics, and reactions have something precise to work from.
Carry what was true then — and only that
Section titled “Carry what was true then — and only that”An event holds the data that was true at the moment it occurred, captured by value. Don’t reference mutable state that might change later, and don’t enrich an event with data a consumer can derive itself. The event should be readable on its own, years from now, without joining against anything.
The five-year test
Section titled “The five-year test”Before you commit an event type, ask: “Will this still make sense to someone reading it in five years, with no other context?” Clear domain naming and self-contained data are what make that a yes. Events are forever — they’re worth a minute of thought.
Events change by migration, never by editing
Section titled “Events change by migration, never by editing”When an event type needs to evolve, you don’t rewrite history — you describe how to read old events as the new shape with an event type migration. Prefer adding a new event or field over overloading an existing event; it keeps each fact singular and clear. See the recipe: Evolve an event’s shape.
Where this leads
Section titled “Where this leads”- Event and Event Type — the mechanics.
- Projections, reducers, and reactors — what consumes your well-modeled events.
- When to use event sourcing — and when modeling events isn’t worth it.