Event Sourcing & CQRS · Event Sourcing Concepts

Event Store Design

Streams, global ordering, subscriptions and implementing a store on Postgres.

An event store provides: append-only streams with expected-version appends, reading a stream in order, a global ordered log (all events, with a position) for projections, and subscriptions (catch-up then live). Options: purpose-built stores (EventStoreDB/KurrentDB, Axon Server), libraries on relational databases (Marten on Postgres, Eventuous, custom tables), or managed options. Kafka is great for distributing events but weak as an event store (no per-stream optimistic concurrency, retention, per-entity reads).

Event store options

Option Strengths Watch out for
EventStoreDB / KurrentDB Purpose-built streams, subscriptions, projections New infrastructure to run
Marten (Postgres, .NET) Event store + document DB on Postgres .NET only
Custom Postgres tables Simple, transactional, familiar Global ordering gaps with concurrent inserts
Kafka Distribution, replay No per-stream concurrency; retention; per-entity reads slow
-- Minimal event store on PostgreSQL
CREATE TABLE events (
  global_position bigserial PRIMARY KEY,
  stream_id       text        NOT NULL,
  stream_version  int         NOT NULL,
  event_type      text        NOT NULL,
  data            jsonb       NOT NULL,
  metadata        jsonb       NOT NULL DEFAULT '{}',
  recorded_at     timestamptz NOT NULL DEFAULT now(),
  UNIQUE (stream_id, stream_version)               -- optimistic concurrency
);

-- Append (expected version 4 → new events get 5, 6); unique violation = concurrency conflict
INSERT INTO events (stream_id, stream_version, event_type, data)
VALUES ('account-42', 5, 'MoneyWithdrawn', '{"amount": 1000}');

-- Projections read the global log
SELECT * FROM events WHERE global_position > $last_checkpoint ORDER BY global_position LIMIT 500;

Common pitfalls

  • Using bigserial order naively (concurrent transactions commit out of order → projections can skip events; use commit-ordered approaches or transaction-id watermarks).
  • Kafka as the only event store.
  • Mutable events (editing history).

Key points

  • Streams + expected version + global log
  • Purpose-built or Postgres-based
  • Kafka distributes, doesn't replace a store

Further reading: Kurrent (EventStoreDB): Event sourcing

Practice questions

Why is Kafka usually not a good event store?

Answer

It lacks optimistic concurrency per entity stream, efficient reads of one entity's events, and indefinite retention by default; it's better for distributing events.

What problem can a simple bigserial global position cause for projections?

Answer

Concurrent transactions can commit out of sequence-number order, so a projection reading 'position > checkpoint' may skip an event committed later with a lower position.

Open in the app to mark lessons complete, take notes and track your progress.