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.