Event Sourcing & CQRS

Traditional databases store the current state of an entity. If you update a user’s address, the old address is overwritten and lost forever.

Event Sourcing is a different approach: you store every change that has ever happened as an immutable sequence of events. Redis Streams is the perfect engine for this.

1. The Pattern

Instead of UPDATE users SET address='...', you append an event: XADD user_events * type "AddressUpdated" new_address "...".

  • Audit Trail: You have a perfect history of everything that happened.
  • Time Travel: You can rebuild the state of the system at any point in time by replaying events up to that timestamp.
  • Debuggability: If a bug corrupts your data, you can fix the code and replay the event log to correct the database.

2. CQRS (Command Query Responsibility Segregation)

Event Sourcing often pairs with CQRS.

  1. Write Side (Command): App writes events to the Redis Stream. This is fast and simple.
  2. Read Side (Query): Workers consume the stream and update “Read Models” (e.g., Redis Hashes, SQL tables, Search Indexes) optimized for queries.

3. Interactive: Event Replay

Observe how the “Current State” is just a derivation of the “Event Log”. Try replaying the events to rebuild the user’s profile.

Event Log (Source of Truth)

No events yet

Current State (Derived)

{}

4. Code Examples: State Rebuilder

package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

    // Read all history
    streams, _ := rdb.XRead(ctx, &redis.XReadArgs{
        Streams: []string{"user_events", "0"},
        Count:   1000,
        Block:   0,
    }).Result()

    userState := make(map[string]string)

    for _, stream := range streams {
        for _, event := range stream.Messages {
            values := event.Values
            eventType := values["type"].(string)

            // Reducer Logic
            if eventType == "UserCreated" {
                userState["id"] = values["id"].(string)
                userState["name"] = values["name"].(string)
            } else if eventType == "EmailUpdated" {
                userState["email"] = values["email"].(string)
            }
        }
    }

    fmt.Println("Rebuilt State:", userState)
}

5. Summary

By combining Redis Streams with Event Sourcing, you gain a powerful architecture where your data is durable, auditable, and replayable. You are no longer just storing the “now”—you are storing the entire history of your business.