Stackbook Logo
data-consistency

Event Sourcing Adoption Framework

Intent

Decide whether and how to store state as an immutable sequence of events.

Problem

Systems that overwrite current state lose the history of how they got there, which hurts auditing, debugging, and temporal queries. Teams sometimes adopt event sourcing for fashion and then struggle with versioning, projections, and operational complexity. A structured adoption path avoids both missing the benefits and paying the cost needlessly.

Forces

    Solution

    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

    1. Qualify the candidate: confirm audit, temporal, or replay value for a single bounded context, not the whole estate.
    2. Model events in past tense using business language, such as OrderPlaced, and keep them small and intention-revealing.
    3. Choose a store: a purpose-built event store or an append-only table with a unique constraint on stream id and version.
    4. Implement aggregates that apply events through a pure function, separate from command handling.
    5. Build projections with checkpointing so they resume after failure, and make them idempotent.
    6. Define an upcasting strategy for evolving events before the first release.
    7. 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)
    );
    -- Append with expected version: a duplicate key means a concurrent writer won.
    

    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.

    ✓ When to Use

    • •Regulatory or business needs demand a complete, tamper-evident audit trail
    • •The domain is naturally event-driven, such as ledgers or order lifecycles
    • •You need to rebuild or add read models from history
    • •Temporal queries about past state are valuable

    ✗ When Not to Use

    • •The domain is simple CRUD with no history requirement
    • •The team lacks experience with eventual consistency and asynchronous projections
    • •Personal data must be erased and cannot be handled by crypto-shredding

    Pros

    • +Complete audit history by construction
    • +Read models can be rebuilt or added later from the event log
    • +Natural fit for event-driven integration
    • +Enables debugging by replaying production sequences

    Cons

    • −Event schema evolution is permanent and demanding
    • −Projections are eventually consistent
    • −Querying current state needs separate read models
    • −Steep learning curve and specialised tooling

    Cost Profile

    Infrastructure

    Operational

    Cognitive

    Failure Modes

      Alternatives

        Related Patterns

          Competency Domains

          data stateeconomics evolutionsecurity compliance