Event sourcing and CQRS get talked about more than they get used. For 90% of products they’re overkill. For the 10% where they fit, they’re transformative. This post is the working knowledge.

The two ideas

Event sourcing

Instead of storing current state, store the sequence of events that produced the state. Current state is a fold over events.

events: [
  {OrderCreated, id: 42, total: 100},
  {OrderPaid, id: 42, amount: 100},
  {OrderShipped, id: 42, tracking: "..."},
]

State is rebuildable from events. New views are added by replaying. Every change has provenance.

CQRS — Command Query Responsibility Segregation

Separate the model used for writing from the model used for reading.

Writes → Command model → events / state
                           ▼ (project)
                       Read model
Reads ←──────────────────┘

Different shapes optimized for different access patterns.

When each pays off

Event sourcing

  • Audit / compliance is core (finance, healthcare).
  • Replay is needed (debug a bug from 6 months ago).
  • Multiple read models from the same data (reports, analytics, search).
  • Temporal queries (“what was the state on March 5?”).

CQRS without event sourcing

  • High read:write asymmetry.
  • Reads need denormalized views the write side doesn’t.
  • Eventually-consistent reads are acceptable.

You can do CQRS with classic Postgres (write tables + materialized read views). Don’t need event sourcing.

When NOT to use them

  • Standard CRUD app. Postgres + a single model is simpler.
  • Small team unfamiliar with the patterns. Cost of getting it wrong > benefit.
  • Workloads with strong consistency requirements where eventual consistency is unacceptable.

Pragmatic event sourcing

The classic objection: rebuilding state from millions of events is slow. Solutions:

  • Snapshots: periodically materialize state; replay only events since last snapshot.
  • Bounded streams: per-aggregate (per-order, per-account), not one global stream.
  • Read models: most queries hit projections, not the event log.

Storage: a single Postgres table works to surprising scale.

CREATE TABLE events (
    id BIGSERIAL PRIMARY KEY,
    aggregate_type TEXT NOT NULL,
    aggregate_id TEXT NOT NULL,
    event_type TEXT NOT NULL,
    payload JSONB NOT NULL,
    sequence_no INT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now(),
    UNIQUE(aggregate_type, aggregate_id, sequence_no)
);

CREATE INDEX events_aggregate ON events(aggregate_type, aggregate_id, sequence_no);

Optimistic concurrency via the unique constraint on sequence_no.

Projections

-- A projection table
CREATE TABLE order_summary (
    order_id BIGINT PRIMARY KEY,
    customer TEXT,
    total_cents INT,
    status TEXT,
    last_event_id BIGINT
);

A worker subscribes to events, updates the summary. Reads hit the summary, not the event log.

For the streaming pattern see Postgres CDC, Logical Replication, Debezium .

Tools

Strengths
EventStoreDBPurpose-built event store
Postgres + customWorks at most scales; no new infra
KafkaHigh throughput; replay; at-least-once
NATS JetStreamLight, low-latency
Marten (.NET)Postgres-backed event sourcing library
Eventstore PG (Python)Postgres-backed library

For most teams: Postgres + custom. Add Kafka if you need fan-out at high throughput.

For broker selection see Kafka vs NATS vs RabbitMQ .

Common pitfalls

1. Schema migrations

Events live forever. Rename a field; old events still have the old name. Plan for it: version events, or keep old shapes readable forever.

2. Eventual consistency surprises

Read model lags write. The user creates an order then sees an empty order list because the projection hasn’t caught up. UI must handle this.

3. Over-modeling

Not every change needs to be an event. “User changed display name” can be a state mutation. Reserve events for domain-meaningful transitions.

4. No durable workflow story

Event sourcing handles state; not workflows. For multi-step processes use Temporal Durable Execution .

Read this next

If you want a Postgres-based event-sourcing template (events table, projection workers, snapshot logic), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .