Event Sourcing Adoption Framework
Event sourcing stores facts rather than current state. The balance of an account is not a column but the sum of deposit and withdrawal events. This framework helps decide where that model pays off and how to introduce it without destabilising the wider system.
How it works
Each aggregate has an event stream. A command is validated against state rebuilt from past events, and if accepted it appends new events using optimistic concurrency on the expected stream version. Subscribers project events into read models, search indexes, or integration messages. Snapshots store periodic aggregate state so replay stays fast for long streams.
Implementation steps
- Qualify the candidate: confirm audit, temporal, or replay value for a single bounded context, not the whole estate.
- Model events in past tense using business language, such as OrderPlaced, and keep them small and intention-revealing.
- Choose a store: a purpose-built event store or an append-only table with a unique constraint on stream id and version.
- Implement aggregates that apply events through a pure function, separate from command handling.
- Build projections with checkpointing so they resume after failure, and make them idempotent.
- Define an upcasting strategy for evolving events before the first release.
- Plan for personal data with crypto-shredding: encrypt sensitive fields with per-subject keys and delete the key to erase.
Common pitfalls
- Treating events as database change notifications rather than domain facts.
- Publishing internal events as the public integration contract, freezing the internal model.
- Skipping projection rebuild drills until a real incident.
- Very long streams with no snapshot policy.
- Fixing mistakes by editing events instead of appending compensating events.
Metrics to watch
- Projection lag per read model.
- Aggregate stream length percentiles and replay time.
- Optimistic concurrency conflict rate.
- Time to rebuild a projection from scratch.
- Count of event versions in active use.
Example configuration
CREATE TABLE events (
stream_id TEXT NOT NULL,
version INT NOT NULL,
event_type TEXT NOT NULL,
schema_ver INT NOT NULL DEFAULT 1,
data JSONB NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (stream_id, version)
);
Practical rollout
Start with a time-boxed pilot on one aggregate type, such as a ledger or order lifecycle, and define success criteria beforehand: audit questions answered, projection rebuild time, and developer productivity. Review the pilot with the team that will operate it. If the benefits are mostly auditability, compare against a simpler audit table before committing. Expand only after the versioning and rebuild processes have survived at least one real change to an event shape.
Key takeaways
- Adopt event sourcing per bounded context where history is a requirement.
- Invest in versioning, upcasting, and rebuild tooling before production.
- Keep internal events separate from public integration events.
- Plan privacy erasure up front with crypto-shredding.