Fintech is a high-load problem with an extra constraint that changes everything: being wrong is worse than being slow. A social feed can drop an event and nobody notices; a payment system that double-debits an account has a legal problem, a support problem, and a trust problem it may not recover from. Every choice below follows from that asymmetry.
The non-negotiables
- Money is integer minor units — cents, tiyin, satoshi — with an explicit currency. Never a float, ever, anywhere, including in JSON and in your analytics pipeline.
- Double-entry, append-only ledger. Entries are never updated or deleted; a correction is a new compensating entry. Every transaction balances to zero.
- Idempotency keys on every mutating endpoint, stored with the response, so a client retry after a network timeout returns the original result instead of charging twice.
- Full audit trail: who did what, when, from where, with the before and after state. Assume you will have to reconstruct any balance at any historical timestamp.
- Reconciliation as a first-class system, not a script — automated daily against every external provider, with a defined process for breaks.
Core services
Java/Kotlin or Go for the transaction core. Both are boring in the right way: mature, strongly typed, heavily used in finance, with excellent tooling and a hiring pool. I lean toward the JVM for the ledger itself — the domain logic gets complex, and the ecosystem for financial modelling, testing, and static analysis is deeper. Go for the surrounding high-throughput services: gateways, notification, ingestion. Keep the ledger small, boring, and heavily tested; put the interesting business logic around it, not inside it.
Data layer
PostgreSQL for the ledger, with serializable or repeatable-read isolation on balance-affecting paths, and constraints that make an unbalanced entry physically impossible to insert. This is the one place to prefer correctness over throughput unconditionally. Partition the ledger by time, keep hot balances in a materialized projection, and scale reads with replicas — accepting that a balance read served from a replica may be stale, which means balance checks that authorize money must hit the primary.
-- Append-only, self-balancing double entry
CREATE TABLE ledger_entries (
id BIGSERIAL PRIMARY KEY,
transaction_id UUID NOT NULL,
account_id UUID NOT NULL,
amount_minor BIGINT NOT NULL, -- signed; credits +, debits -
currency CHAR(3) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ON ledger_entries (transaction_id, account_id);
REVOKE UPDATE, DELETE ON ledger_entries FROM application_role;
-- Every transaction must sum to zero, enforced by a deferred constraint trigger
-- rather than by hoping the application got it right.- Kafka for the event backbone — durable, replayable, ordered per partition (partition by account so one account's events stay in order).
- Redis for rate limiting, idempotency-key lookups, and short-lived session state. Never as the source of truth for a balance.
- ClickHouse for analytics, fraud features, and regulatory reporting, fed from the event stream. Keep analytical load off the ledger entirely.
- S3-compatible object storage with legal hold for documents, statements, and KYC artifacts.
Correctness patterns
Distributed transactions across services are not available to you, so use the saga pattern with explicit compensating actions, and the outbox pattern so that writing to the database and publishing an event cannot diverge. Model each payment as an explicit state machine — initiated, authorized, captured, settled, reversed — with legal transitions enforced in code, because "which states can follow this one" is the question every incident eventually comes down to. And treat every external provider as unreliable by default: timeouts, retries with idempotency, circuit breakers, and a reconciliation pass that catches the cases where you never learned the outcome.
Security and compliance
- Encryption in transit (mTLS between services) and at rest; a real KMS or HSM for keys, with rotation actually practised.
- Tokenize card data and keep it out of your systems entirely — PCI DSS scope you do not have is scope you do not have to audit.
- Least privilege everywhere, with time-bound, approved, fully logged production access. No standing admin credentials.
- KYC/AML screening, sanctions checks, and transaction monitoring as designed subsystems with owners, not features bolted on before an audit.
- Data residency and retention rules encoded in the architecture, since jurisdictions will disagree about where a record may live.
- Immutable, tamper-evident audit logs written to storage the application cannot modify.
In fintech, availability is negotiable and correctness is not. When the two conflict, refuse the transaction — an unhappy user is recoverable, a corrupted ledger is not.
Operations
- 01Kubernetes across at least two availability zones, with a documented and rehearsed disaster recovery plan — measured RPO and RTO, not aspirational ones.
- 02Point-in-time recovery on the ledger database, with restores tested on a schedule. An untested backup is a hypothesis.
- 03Canary deploys with automated rollback, and feature flags on every money-moving change.
- 04Alerting on business invariants, not just infrastructure: ledger imbalance, reconciliation breaks, settlement delay, authorization success rate by provider.
- 05Property-based and simulation testing on the ledger — generate random transaction sequences and assert that the invariants always hold.
- 06Chaos testing against provider failure specifically: what happens when the payment gateway accepts a request and never answers?
None of this is exotic technology. That is the point — the difficulty in fintech is not novel infrastructure, it is the discipline to make correctness structurally enforced rather than carefully remembered.