Architecture
Architecture you can read end-to-end.
Four product apps, one typed API contract, one modular banking core, one
cloud-native footprint described entirely as code. Nothing on this page is
aspirational — every block is locked behind a decision in the project's
DECISIONS.md
and shipped in the codebase you can clone today.
Audience: platform architects and senior engineers evaluating Malai as a licensing or commissioning target.
System overview
Four shipping apps. One deferred surface.
Each app is independently buildable and individually licensable. They share a single typed API contract and one design system.
-
malai-website
Static marketing surface
Pre-rendered HTML served from a CDN with object storage as the origin. Zero-JS by default. The page you are reading right now.
-
malai-webapp
Single-page customer portal
Typed single-page app served from the same CDN. Calls the banking core over HTTPS using a typed API client generated from the shared contract.
-
malai-backend
Modular banking core
JVM-based modular monolith with hard module fences. Runs as a containerised service behind a TLS-terminating ingress; writes to a managed relational database and the append-only audit log.
-
malai-infra
Infrastructure-as-code
Per-environment Terraform footprint (dev, staging, prod) describing every cloud resource: network, container platform, managed database, secret store, CDN, and DNS. A fresh environment stands up in minutes.
-
malai-mobile
PWA — deferred to v2
The mobile surface ships as a Progressive Web App reusing the malai-webapp bundle. Out of scope for v1; shown here for completeness.
Backend service boundaries
One container, three modules, hard walls.
Malai is a modular banking core today, split into independent containers
in v2. The walls between
auth,
account-holder, and
account are enforced
at build time, so a cross-module import fails CI before it lands.
-
auth
Login, session, token issuance
Owns the login flow, refresh tokens, and standards-based session config. Issues short-lived JWT bearer tokens signed with a managed key held in the secret store. The only module that ever touches a password hash, and the only seam where the identity provider could be swapped.
-
account-holder
Customer profile, KYC stubs, addresses
Owns the human side of the customer record. Holds names, dates of birth, addresses, contact details, and the verification status stubs that will become real KYC checks in v2.
-
account
Sort code · balances · transactions · transfers
Owns the money side. UK sort code (6) + account number (8) format is enforced at the column level. All balance changes go through the double-entry ledger; nothing mutates a balance column directly.
Data flow on a transfer
One outgoing payment, six steps.
The credibility moment. This is what a reviewer looks for: idempotency, double-entry, audit trail, real status codes.
-
POST /transfers
Webapp sends the request over HTTPS with an Idempotency-Key header and a short-lived bearer token. Retrying with the same key is safe — the second call returns the first call's result.
-
Ingress → banking core
The public edge terminates TLS, forwards into the private network, and lands on a banking-core task running inside the container platform.
-
Auth filter validates the session
Standards-based session middleware verifies the bearer-token signature against a managed key, loads the customer principal, and propagates the trace context.
-
Account module writes ledger_entries
Two rows inserted in one transaction: a debit on the source account, a credit on the destination. A database-level trigger refuses any transaction whose entries do not sum to zero.
-
Audit log append
The mutation is appended to an immutable audit table. Each row carries a hash of the previous row, so tampering breaks the chain visibly.
-
202 Accepted
Banking core returns 202 with the transfer id. The webapp shows the optimistic confirmation; the typed data layer reconciles on the next poll.
Cloud topology
The network, drawn honestly.
Public, private, isolated. The shape every cloud review starts with.
dev, staging,
and prod — described
end-to-end as Terraform. Remote state is encrypted, versioned, and lock-protected.
Every resource carries the same environment, owner, and cost-centre tags. A
fresh environment stands up from a clean cloud account in minutes.
What's deliberately not v1
The roadmap, written down.
We picked production-shaped over over-promising. These are the v2
candidates — each one is a separate decision under
DECISIONS.md, not vapour.
-
Per-service containers
Split the modular banking core into independent containerised services on the same cluster. The build-time module boundary becomes a network boundary, with mutually authenticated calls between services.
-
Event-driven fan-out
An event-streaming backbone makes audit-log fan-out the natural first topic; transaction-posted events then feed downstream fraud, notification, and analytics consumers without coupling them to the banking core.
-
OPA policy engine
Externalise authorisation policies that today live inline in the auth module. Reviewers evaluating compliance tooling expect a policy engine like OPA in the diagram.
-
malai-mobile (PWA, then optional native)
Reuse the webapp bundle behind a Progressive Web App manifest with offline-capable caching. A native mobile path remains a v3 option for biometric and push parity on iOS.