Skip to content

A practical implementation of Event Sourcing and CQRS patterns in Go, demonstrated through a banking domain.

Notifications You must be signed in to change notification settings

0xb0b1/eventsource

Repository files navigation

EventSource

A practical implementation of Event Sourcing and CQRS patterns in Go, demonstrated through a banking domain.

Features

  • Event Store: Append-only event log with PostgreSQL backend
  • Aggregate Roots: Bank Account aggregate with domain logic
  • CQRS: Separate command and query models
  • Event Projections: Real-time read models for account balances and transaction history
  • Snapshots: Performance optimization for aggregates with many events
  • Optimistic Concurrency: Version-based conflict detection
  • Event Replay: Rebuild projections from event history
  • Temporal Queries: View aggregate state at any point in history

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                           Client                                     │
└─────────────────────────────────────────────────────────────────────┘
                    │                           │
                    │ Commands                  │ Queries
                    ▼                           ▼
┌─────────────────────────────┐   ┌─────────────────────────────────┐
│      Command Handler        │   │         Query Service            │
│  (OpenAccount, Deposit,     │   │   (GetAccount, ListAccounts,    │
│   Withdraw, Transfer)       │   │    GetTransactions)              │
└─────────────────────────────┘   └─────────────────────────────────┘
           │                                    │
           ▼                                    │
┌─────────────────────────────┐                │
│     Aggregate Root          │                │
│     (BankAccount)           │                │
└─────────────────────────────┘                │
           │                                    │
           ▼                                    │
┌─────────────────────────────┐   ┌────────────┴────────────────────┐
│       Event Store           │──▶│         Projections             │
│   (Append-only log)         │   │  (AccountBalance, Transactions) │
└─────────────────────────────┘   └─────────────────────────────────┘
           │                                    │
           └────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   PostgreSQL    │
                    └─────────────────┘

Domain Events

Event Description
AccountOpened A new bank account was created
MoneyDeposited Money was deposited into an account
MoneyWithdrawn Money was withdrawn from an account
TransferSent Money was sent to another account
TransferReceived Money was received from another account
AccountClosed An account was closed

Quick Start

Using Docker Compose

# Start PostgreSQL and the application
docker-compose up -d

# Check logs
docker-compose logs -f eventsource

Local Development

# Start PostgreSQL and run migrations
make dev-db

# Run the server
make dev

API Usage

Commands (Write Side)

Open Account

curl -X POST http://localhost:8080/api/v1/accounts \
  -H "Content-Type: application/json" \
  -d '{
    "owner_name": "John Doe",
    "initial_deposit": "1000.00"
  }'

Response:

{
  "success": true,
  "data": {
    "account_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Deposit Money

curl -X POST http://localhost:8080/api/v1/accounts/{account_id}/deposit \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "500.00",
    "description": "Salary deposit"
  }'

Withdraw Money

curl -X POST http://localhost:8080/api/v1/accounts/{account_id}/withdraw \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100.00",
    "description": "ATM withdrawal"
  }'

Transfer Money

curl -X POST http://localhost:8080/api/v1/accounts/{from_account_id}/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "to_account_id": "destination-account-id",
    "amount": "250.00",
    "description": "Payment for services"
  }'

Close Account

curl -X POST http://localhost:8080/api/v1/accounts/{account_id}/close \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Account no longer needed"
  }'

Queries (Read Side)

List Accounts

curl "http://localhost:8080/api/v1/accounts?limit=10&offset=0"

Get Account Balance

curl http://localhost:8080/api/v1/accounts/{account_id}

Get Transaction History

curl "http://localhost:8080/api/v1/accounts/{account_id}/transactions?limit=50"

Get Raw Events (Event Sourcing)

curl http://localhost:8080/api/v1/accounts/{account_id}/events

Key Concepts

Event Sourcing

Instead of storing the current state, we store all events that led to that state. The current state is derived by replaying events.

// Events are immutable facts
event := NewMoneyDeposited(accountID, version, amount, "Salary")

// State is rebuilt by applying events
for _, event := range events {
    account.ApplyEvent(event)
}

CQRS (Command Query Responsibility Segregation)

Commands (writes) and Queries (reads) use different models optimized for their purpose.

  • Command Model: Aggregates with business logic
  • Query Model: Denormalized read projections

Optimistic Concurrency

Version-based conflict detection prevents concurrent modifications:

-- Only insert if version matches expected
INSERT INTO events (...) WHERE current_version = expected_version

Snapshots

For aggregates with many events, snapshots cache the state periodically:

// Create snapshot every 100 events
if account.Version() % 100 == 0 {
    snapshot := account.ToSnapshot()
    store.SaveSnapshot(snapshot)
}

Configuration

Flag Environment Variable Default Description
-http-addr HTTP_ADDR :8080 HTTP server address
-database-url DATABASE_URL postgres://localhost:5432/eventsource PostgreSQL connection URL

Development

# Run tests
make test

# Run tests with coverage
make test-coverage

# Lint code
make lint

# Build binary
make build

Project Structure

eventsource/
├── cmd/server/          # Application entry point
├── internal/
│   ├── aggregate/       # Domain aggregates (BankAccount)
│   ├── api/             # REST API handlers
│   ├── command/         # Command definitions and handlers
│   ├── event/           # Domain events
│   ├── projection/      # Read model projections
│   └── store/           # Event store implementation
├── pkg/es/              # Reusable event sourcing primitives
├── migrations/          # Database migrations
└── README.md

License

MIT

About

A practical implementation of Event Sourcing and CQRS patterns in Go, demonstrated through a banking domain.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published