Skip to content

Reference architecture

Double-entry financial ledger with event sourcing and Kafka

An immutable double-entry ledger where every posting balances, a retried request never posts twice, and any account's history can be replayed for auditors.

Design constraints

What the design has to hold to

Targets for the scenario this reference is sized for. A real engagement starts by replacing them with your own numbers.

  • 01Double-entry bookkeeping: debits equal credits in every entry
  • 02Strict immutability: no UPDATE or DELETE on ledger rows
  • 03Idempotent API execution, so network retries never post twice
  • 04Transaction commit latency under 50 ms

Component topology

System components and technologies

Subsystems with separate responsibilities, clear contracts between them and storage that scales on its own. The stack named for each is typical, not mandatory.

Stack topology

Double-entry financial ledger with event sourcing and Kafka

Illustrative reference architecture

  1. 01

    Transaction gateway

    Idempotency key validation and balance checks

    Go (Gin) or Rust (Axum) service

  2. 02

    Immutable event stream

    Ordered, durable transaction log partitioned by account_id

    Apache Kafka / Confluent Cloud

  3. 03

    Ledger storage engine

    Append-only double-entry ledger entries and account balances

    PostgreSQL with strict constraint triggers

  4. 04

    Read model projector

    Live account balance projections for the customer UI

    Redis in-memory key-value store

Subsystem 01

Transaction gateway

Idempotency key validation and balance checks

Typical stack

Go (Gin) or Rust (Axum) service

Subsystem 02

Immutable event stream

Ordered, durable transaction log partitioned by account_id

Typical stack

Apache Kafka / Confluent Cloud

Subsystem 03

Ledger storage engine

Append-only double-entry ledger entries and account balances

Typical stack

PostgreSQL with strict constraint triggers

Subsystem 04

Read model projector

Live account balance projections for the customer UI

Typical stack

Redis in-memory key-value store

Data lifecycle

End-to-end data flow

  1. The payment client submits a transaction with an Idempotency-Key header to the transaction gateway.

  2. The gateway checks Redis for that key; if it hasn't been processed, it reserves a pending lock.

  3. The transaction is validated against double-entry rules and published to an account-partitioned Kafka topic.

  4. The ledger consumer writes an append-only record to PostgreSQL inside a single ACID transaction.

  5. The balance projector updates the cached Redis balance, and the confirmation goes back to the caller.

Reliability and resilience

Failure modes and how each is contained

Failure mode 01

Concurrent overdraft race condition

Mitigation

Use PostgreSQL row-level locks (SELECT … FOR UPDATE) or optimistic version numbers on balance rows.

Failure mode 02

Duplicate webhook or retry submissions

Mitigation

Put a UNIQUE constraint on the idempotency_key column, keep keys for a retention window (for example 24 hours) and purge expired ones with a scheduled job.

Failure mode 03

Kafka consumer desynchronization

Mitigation

Keep monotonic sequence IDs per account and raise a reconciliation alert on any gap.

Questions

What teams ask about this design

Event sourcing keeps the full history of every money movement, so any account's state at any point in time can be rebuilt for an audit or a dispute.

The ledger is append-only, so a mistake is corrected by posting a compensating journal entry, never by editing the original record.

Planning a system like this?

Send us your requirements, expected load and budget. We'll reply within one business day with an honest read on the design, and on whether we're the right team to build it.