Skip to content

Latest commit

 

History

History

simulate_cqrs

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Simulate CQRS Using Docker

Tech Stack

  • Programming Language: Go 1.26.0
  • Container: Docker Compose
  • Database: PostgreSQL 18 (separate write store + read store)

Overview

CQRS (Command Query Responsibility Segregation) splits a system into two models that are optimized independently:

  • Commands (writes) hit a normalized write store — one fact in one place, enforced with foreign keys and transactions.
  • Queries (reads) hit a denormalized read store — pre-joined, pre-aggregated rows answered with a single-row lookup.

The two stores are kept in sync asynchronously by a projector that drains a transactional outbox on the write side. That makes reads eventually consistent: a just-committed write is invisible to queries until the projector runs — and this project shows exactly that window.

Related projects: simulate_read_write_splitting splits reads/writes across copies of the same model; CQRS splits them across different models. simulate_query_caching is a simpler form of the same read-optimization idea.

Key Concepts Demonstrated

  1. Command/Query separation — different code paths, different schemas.
  2. Transactional outbox — events written atomically with the data.
  3. Projection — rebuild denormalized read rows from normalized events.
  4. Eventual consistency — query before vs after projection.

Architecture

   COMMAND ──► ┌─────────────── write_db (5460) ───────────────┐
  PlaceOrder   │ customers / orders / order_items (normalized) │
  UpdateStatus │ events  (outbox, written in same TX)          │
               └───────────────────────┬───────────────────────┘
                                        │ poll unprocessed events
                                  ┌─────▼──────┐
                                  │  Projector │  JOIN + aggregate
                                  └─────┬──────┘
               ┌───────────────── read_db (5461) ──────────────┐
   QUERY ─────►│ order_summary (denormalized, 1 row per order) │
 GetOrderSummary└──────────────────────────────────────────────┘

How to Run

docker compose up -d      # write store + read store
docker compose ps         # wait until both healthy
go mod tidy
go run main.go

Expected Output

A placed order is immediately queryable on the write store but absent from the read model until the projector runs. After projection, the query returns a single denormalized row (customer name, item count, total). A status change repeats the cycle: stale read → project → fresh read.

CQRS Trade-offs

Aspect Benefit Cost
Read speed Single-row lookups, no JOINs Read model is derived/duplicated data
Write clarity Normalized, transactional Two schemas to maintain
Scaling Scale reads and writes independently Projector is extra moving infra
Consistency Tunable, async Reads are eventually consistent

Cleanup

docker compose down -v