Skip to content
All studies

Financial systems / Independent study

Money Movement & Ledger

Balances should be explainable.

Holds, settlement and reversals modelled as balanced journal entries, not destructive balance updates.

Scope

Architecture proposal, OpenAPI sketch and tested browser model. No production backend or employer data.

Proposed backend: Java · Spring Boot · PostgreSQL · Transactional outbox

Executable model: TypeScript

The problem

A mutable balance says how much is there now, but not why. Holds, settlements and reversals become difficult to investigate when each operation overwrites the last result or publishes an event separately from its database commit.

The approach

Represent each money movement as a balanced transaction in an append-only journal. Separate available funds from held funds. Settle from the hold, release an unused hold, and reverse a settlement with compensating entries rather than editing history.

Architecture

Append evidence. Preserve balance.

Proposed system boundariessync / durable / async
  1. 01

    Command API

    Validate currency, amount, identity and command key.

  2. 02

    Ledger transaction

    Lock affected accounts in a stable order; enforce funds and balanced postings.

  3. 03

    Read models

    Expose available, held and settled views with a projection version.

Durable boundary

PostgreSQL: command receipt + journal transaction + postings + outbox record, committed together. One currency per transaction. Balance projections remain verifiable against the journal.

Asynchronous work

Outbox relay → event bus → reporting projections. Consumers track event IDs and account sequence numbers. An event stream is not a substitute for the accounting source of truth.

Logical boundaries, not a deployed topology. The interactive model below covers state transitions only.

Exercise the failure path

Follow the money, entry by entry.

Start with a fictional CAD 1,000.00 balance. Hold CAD 250.00, settle or release it, and inspect a reversal.

Interactive model
Available
$1,000.00
Held
$0.00
Payee
$0.00
Transfer state
ready
Append-only journal · illustrative CAD amounts
TransactionAccountPosting
funding-001funding-$1,000.00
funding-001available$1,000.00

Sum of all signed postings: $0.00. The funding counter-account is included.

Fictional opening balance: CAD 1,000.00. Place a hold to start.

Model limits: One account pair and one transfer in browser memory. Signed postings illustrate conservation; this is not a full chart of accounts, database transaction or deployable financial ledger.

API design

An explicit command boundary.

Download OpenAPI sketch

Illustrative request and response. This contract specifies one command, not the entire proposed API.

POST /v1/holds

Idempotency-Key: command_demo

{
  "accountId": "wallet_demo",
  "amountMinor": 25000,
  "currency": "CAD"
}

Response

201 Created
Location: /v1/holds/hold_demo

{ "id": "hold_demo", "status": "held", "amountMinor": 25000 }

Technical decisions

What I would choose. What it costs.

01Integer minor units

Use integer CAD cents in this model. A backend would use bounded integer or exact decimal storage with currency-specific precision.

Trade-off: Multi-currency and FX require explicit rounding policies and balancing accounts. They are deliberately out of scope.

02Serialize the conflicting writes

Lock accounts in a consistent order within a short transaction and check available funds before posting.

Trade-off: Hot accounts can bottleneck. Measure contention before introducing partitioning or more complex concurrency control.

03Compensate, don't erase

A reversal references the original settlement and posts the opposite entries.

Trade-off: A longer journal, but the audit trail remains intelligible. Real reversals also need funds and policy checks.

Operating the system

What needs attention in production.

Accounting invariants

Check that every transaction balances per currency and that stored balance projections agree with the journal. Investigate discrepancies rather than auto-correcting history.

Delivery lag

Monitor oldest unpublished outbox event and projection lag. Expose read freshness instead of presenting stale data as authoritative.

Recovery boundaries

Rebuild projections from the journal. Test backup restoration and replay on isolated data before allowing operator recovery actions.

Outcome & limits

What this study establishes.

The executable model derives balances from its journal. Each operation adds two offsetting entries, duplicate commands do not post again, and a reversal preserves the original settlement. Tests cover those properties and invalid transitions.

View study source

Next steps

Before calling it production-ready.

  • Implement durable command receipts and concurrent spend tests with PostgreSQL.
  • Add partial settlement, hold expiry and explicit reversal constraints.
  • Test outbox redelivery, projection rebuilding and single-currency accounting reconciliation.

Start a conversation

Building a backend team?

I'm interested in senior backend and Tech Lead roles across fintech, payments, SaaS and platform engineering in Canada.

Next study: Architecture Decision Hub