Case study · Reference platform
Double-Entry Ledger Platform
A production-minded modular monolith for wallets and double-entry ledger flows in fintech and payments — balanced postings, idempotent commands, and a live demo on the real API.
Not a balances spreadsheet. Runnable Compose stack with append-only journals, transactional outbox, maker/checker withdrawals, and a showcase UI that hits live endpoints.
Signals
Append-only
Journal
Flyway-backed triggers block UPDATE/DELETE on journal_entries.
Idempotent
Commands
Scoped advisory locks + idempotency keys inside posting transactions.
Outbox
→ Kafka
Events written with the post, then published by OutboxDispatcher.
Golden posting rule
Every money movement is a balanced journal_entry set. Retries and duplicate provider refs dedupe via idempotency keys inside the posting transaction. DB triggers reject UPDATE/DELETE on the journal — corrections are new postings, never silent edits.
Problem
Wallet and payment products need auditable money movement under retries, webhooks, and ops pressure. Spreadsheet-style balances and destructive updates fail compliance. Without idempotency, a client timeout or duplicate provider callback creates double posts. Without an outbox, Kafka can advertise state that never committed.
Solution
Modular monolith: domain, persistence, application services, and HTTP adapter as Maven modules. Product flows in PaymentApplicationService — P2P transfer, merchant payment (gross + platform fee), withdrawal (optional maker/checker), reversal, and deposit settlement from signed provider webhooks. Live demo UI at / exercises the real API.
Constraints
- Balanced postings only — account locking and negative-liability guards on the critical path.
- Idempotency keys and provider refs must dedupe inside the posting transaction.
- Above ledger.withdrawal.approval-threshold-minor, withdrawals return 202 + pending approval id.
- Demo faucet (/api/v1/demo/**) gated by DEMO_MODE_ENABLED — never ship to production.
- Webhook HMAC (WEBHOOK_HMAC_SECRET) required for provider deposit settlement.
Architecture flow
- 01HTTP / Webhooks
- 02Payment services
- 03Ledger posting
- 04Outbox / Kafka
- 05Reconciliation
Architecture
ledger-domain (types/models) → ledger-persistence (JPA, Flyway) → ledger-core (posting, payments, wallet, outbox, reconciliation, webhook verification) → ledger-platform (Spring Boot packaging, REST, security, OpenAPI). Compose: PostgreSQL 16, Redis 7, Kafka, Prometheus, Grafana. Optional OTLP via Micrometer/OpenTelemetry.
Decisions
- 01
Modular monolith, not microservices
Clear Maven boundaries without the ops tax of split deployables — extract later if needed.
- 02
Append-only journal at the DB
Triggers enforce immutability so application bugs cannot silently rewrite history.
- 03
Transactional outbox
outbox_messages and the post commit together; OutboxDispatcher publishes to Kafka.
- 04
Idempotency inside the transaction
Keys + scoped advisory locks so retries and duplicate provider refs cannot double-post.
- 05
Maker/checker for large withdrawals
Threshold in minor units; COMPLIANCE / OPERATIONS / ADMIN approve or reject.
- 06
Demo UI on the real API
Bundled static showcase with JWT login, faucet, transfers, journal expand, and call log — reviewers see real HTTP, not mocks.
Trade-offs
A modular monolith is simpler to run than split services, but module discipline must hold. Append-only + deterministic locking buys correctness at the cost of stricter concurrency on hot accounts. Demo mode accelerates review and must stay off outside sandboxes. Local Compose includes ZooKeeper for Kafka — fine for the lab, not the target production topology.
Stack
What the platform already covers
- 01
Product flows: P2P, merchant pay with fee split, withdraw, reverse, deposit settlement.
- 02
Ops: wallet/account freeze, trial balance, pending approvals, audit in the service layer.
- 03
Integrity: journal immutability triggers, deadlock-safe lock order, reconciliation + balance snapshot jobs.
- 04
Observability: Prometheus scrape, Grafana dashboard provisioned, optional OTLP export.
- 05
Tests: mvn test across modules; Testcontainers ITs when Docker is available.
Scoping a ledger or wallet problem?
Share posting invariants, webhook constraints, and ops needs — I will map architecture, risks, and a realistic first slice.
