Events & the outbox
Every command declares the events it emits. When it runs, those events are written in the same database transaction as the change itself.
That single sentence is the guarantee. If the order was created, the order.created event exists. If the
transaction rolled back, neither does. There is no window where the change happened and the notification did
not, and no window where you were told about something that then un-happened.
This is the transactional outbox pattern, and it is the difference between an event stream you can build on and one you have to reconcile against.
What the outbox is
Section titled “What the outbox is”An append-only table. Events go in; nothing edits them and nothing deletes them. It is the ledger of what happened, in the order it happened.
Delivery is tracked separately, per consumer — the question the system asks is “what does this consumer still owe?”, not “where is the global cursor?”. Two things follow that matter to you:
- A slow or broken consumer does not hold anyone else back. Its own backlog grows; the others drain.
- A new consumer can be added without rewinding anything. It gets its own accounting from the start.
What you must build for
Section titled “What you must build for”Delivery is at-least-once. Your consumer will see the same event twice — after a retry, after a redeploy, after a network timeout that succeeded on the far side.
So the rule is not negotiable: consumers must be idempotent. Processing an event twice must have the same effect as processing it once. In practice that means keying your side-effects on something in the event rather than on the fact that you received it.
Order is per cycle, not global. Events carry a sequence that orders them within a delivery cycle. Do not build logic that assumes a total global ordering across everything that ever happened.
Events vs decisions
Section titled “Events vs decisions”They are opposites, and picking the wrong one is a real bug:
- An event is a fact that is already true. You react to it. If your reaction fails, the fact stands.
- A decision is a question asked before the fact exists. Your answer shapes it.
If you find yourself reacting to an event by trying to change what it describes, you wanted a decision.
Reading what a command emits
Section titled “Reading what a command emits”Every command’s reference page lists its events — and, for commands that emit none, says why. A silent command carries its reason rather than a bare “none”, because “deliberately quiet” and “nobody got to it yet” are very different facts to build on.