Lab 06: Event Sourcing

Time: 60 minutes | Level: Architect | Docker: golang:1.22-alpine

Overview

Event sourcing in Go: Event interface, EventStore with optimistic concurrency, Aggregate Apply/Record pattern, read model Projections, snapshot pattern, and in-memory implementation (production: SQLite/PostgreSQL backend).


Step 1: Core Event Types

package eventsourcing

import (
	"encoding/json"
	"time"
)

// Event: immutable fact that something happened
type Event struct {
	AggregateID string
	Type        string
	Version     int
	Data        json.RawMessage
	OccurredAt  time.Time
	Metadata    map[string]string // correlation_id, causation_id, user_id
}

// Aggregate: entity rebuilt from its event stream
type Aggregate interface {
	AggregateID() string
	Version() int
	Apply(event Event)
	UncommittedEvents() []Event
	MarkEventsAsCommitted()
}

// EventStore: append-only log of domain events
type EventStore interface {
	Append(aggregateID string, expectedVersion int, events []Event) error
	Load(aggregateID string) ([]Event, error)
	LoadFrom(aggregateID string, fromVersion int) ([]Event, error)
}

Step 2: In-Memory EventStore with Optimistic Concurrency


Step 3: Order Aggregate


Step 4: Projection — Read Model


Step 5: Snapshot Pattern


Step 6: Repository Pattern for Aggregates


Step 7: Event Bus for Cross-Aggregate Communication


Step 8: Capstone — Event Sourcing Demo

📸 Verified Output:


Summary

Component
Pattern
Guarantee

Event

Immutable struct

Append-only audit log

EventStore

Append(id, expectedVersion, events)

Optimistic concurrency

Aggregate

Apply(event) pattern

Rebuild from any point

Projection

Event handler → read model

Eventual consistency

Snapshot

State @ version N

O(1) load after N events

Repository

Load+Save aggregate

Clean domain boundary

Event bus

Async cross-aggregate

Loose coupling

Last updated