Introduction

Most applications store only current state - when something changes, the old data is lost. Event sourcing takes a different approach: store every change as an immutable event, preserving the complete history.

The Problem with State-Based Storage

Traditional applications work like this:

// Traditional approach - only current state
type Account struct {
    ID      string
    Balance int
    Status  string
}

// When balance changes, old value is lost
func (a *Account) Withdraw(amount int) {
    a.Balance -= amount  // History is gone
}

Problems with this approach:

  • Lost History: Can’t see how the account reached its current state
  • No Audit Trail: Compliance and debugging become difficult
  • Limited Queries: Can’t ask “what was the balance yesterday?”
  • Concurrency Issues: Multiple updates can conflict

Event Sourcing Solution

Instead of storing state, store the events that create state:

// Event sourcing approach - store events
type Event struct {
    ID        string
    Type      string
    Data      interface{}
    Timestamp time.Time
}

type AccountCreated struct {
    AccountID string
    InitialBalance int
}

type MoneyWithdrawn struct {
    AccountID string
    Amount    int
}

Benefits:

  • Complete History: Every change is preserved
  • Audit Trail: Natural compliance and debugging
  • Time Travel: Reconstruct state at any point in time
  • Flexibility: New views of data without migration

When to Use Event Sourcing

Event sourcing works well when you need:

  • Audit Requirements: Financial, healthcare, or legal systems
  • Complex Business Logic: Domain events matter to the business
  • Temporal Queries: “Show me last month’s state”
  • High Write Throughput: Events are append-only

Don’t use event sourcing for:

  • Simple CRUD applications
  • Systems where current state is all that matters
  • Teams unfamiliar with the complexity

This guide shows you how to implement event sourcing in Go, from basic concepts to production systems.