Architecture

Event Sourcing

Storing the sequence of events that led to current state rather than current state alone.

Overview

Event Sourcing stores the history of state changes as an immutable sequence of events rather than the current state alone. The current state is derived by replaying all events. This gives you a complete audit trail, temporal queries (what was the state at time T?), and the ability to derive new read models from historical data.

Origin

Greg Young formalised Event Sourcing as a pattern around 2010 through talks and writing, often paired with CQRS. The concept has antecedents in accounting ledgers and financial transaction logs, the original event stores.

Examples

Event-sourced bank account

class BankAccount
  attr_reader :id, :balance, :events

  def initialize(id)
    @id = id
    @balance = 0
    @events  = []
  end

  def deposit(amount, reference:)
    apply_event({ type: 'deposited', amount: amount, reference: reference, at: Time.current })
  end

  def withdraw(amount, reference:)
    raise "Insufficient funds" if amount > @balance
    apply_event({ type: 'withdrawn', amount: amount, reference: reference, at: Time.current })
  end

  def self.reconstitute(events)
    account = new(events.first[:account_id])
    events.each { |e| account.apply_event(e, persist: false) }
    account
  end

  private

  def apply_event(event, persist: true)
    case event[:type]
    when 'deposited' then @balance += event[:amount]
    when 'withdrawn' then @balance -= event[:amount]
    end
    @events << event if persist
  end
end

Replaying events from any point in history gives you the state at that moment. The event log is the source of truth; the balance is a derived projection.

Use Cases

  • 01Financial systems requiring complete, immutable audit trails
  • 02Collaborative editing where conflict resolution requires full history
  • 03Debug-friendly systems: replay events to reproduce any past state
  • 04Deriving new read models from historical data without re-collecting it

When Not to Use

  • //Simple CRUD where audit trails are not required, the complexity far exceeds the benefit
  • //High-volume systems without careful event storage design, event logs grow unboundedly without snapshotting
  • //Teams without deep familiarity with eventual consistency and CQRS

Technical Notes

  • Snapshotting: for aggregates with long event histories, store periodic snapshots and replay only events after the snapshot
  • Event schema evolution is the hardest operational challenge: old events in the log must still be replayable with new code
  • EventStoreDB and Apache Kafka are purpose-built event stores. PostgreSQL with an events table works for moderate-scale systems
  • Event sourcing and CQRS (see below) are complementary but independent. Use one without the other if it fits the problem