Architecture

Technical choices


Every technology justified by a constraint of your trade.

Why Go and PostgreSQL

Go produces a single binary, with no virtual machine and no system dependencies: deploying and updating comes down to replacing one file. Its concurrency model is safe by construction, which matters when several counters write at once.

PostgreSQL brings what an accounting system cannot do without: real ACID transactions, deferred constraints checked at COMMIT, row-level isolation in the database itself, and point-in-time recovery.

Redis is only a fast path: idempotency, till locks, rate limiting. PostgreSQL remains the source of truth — if Redis goes down the system slows, it does not lie.

We use pgx and sqlc rather than an ORM. On financial code we want to read the SQL being executed, not guess it.

Money is never stored as a floating-point number

This is the detail that separates an accounting system from a management application.

  • Amounts are 64-bit integers in minor units. Never float64, never REAL in the database.
  • Rates are NUMERIC(10,6).
  • XAF and XOF have no decimal — the minor unit is the franc. But commission rates produce fractions.
  • So every commission calculation produces a rounding remainder, explicitly posted to a dedicated account. Without it the balance drifts by one franc per operation, and three months later nobody knows where the gap came from.
  • No entry ever mixes two currencies: an XAF ↔ XOF conversion is two entries linked by an FX position account.

The life of an operation

Request lifecycle

0.48s measured / 2.0s budget

  1. Counter 3GUUID idempotency key+320ms
  2. TraefikTLS 1.3 · routing+90ms
  3. IdempotencyRedis · replay-safe+8ms
  4. RBAC + rulesrole · agency · ceilings+25ms
  5. Ledger entryACID · balance = 0+35ms
  6. Sealedchained audit hash≈ 0.48s

The 2s ceiling is a blocking gate in our delivery pipeline — not an intention. Note step 3: a retry after a dropped connection replays the same key and creates nothing.

Step 3 is the most important and the least visible: on an unstable network, it is what guarantees that a teller retrying after a dropped connection does not create a second operation.

Degraded mode is a normal case

The network will go down. The architecture treats that as normal, not exceptional:

  • The counter is a PWA with a local queue; till and entry operations remain possible offline.
  • Every operation carries a client-generated UUID used as the idempotency key. On reconnection the server deduplicates natively — no exotic conflict resolution, no CRDTs.
  • Still barred while offline: any operation requiring real-time partner validation, and any breach of the local float ceiling.

What we do not use

Ruled outWhy
Kafka / message busOversized; one more piece of infrastructure to operate
Micro-servicesOperating cost with no return at this volume
Elasticsearchpg_trgm is enough for fuzzy sanctions-list matching
Kubernetes at launchDocker on one server; migrate the day load justifies it