Skip to main content

Architecture Decision Records (ADRs)

The decision log for DemozPay's engineering choices.

Why ADRs?

Six months from now, someone — maybe you — will ask "wait, why did we pick PostgreSQL over MongoDB?" or "why is the ledger a separate service?" ADRs answer that, in writing, with the alternatives we considered. They are the difference between "we just did it" and "here's the reasoning."

When to write one

Write an ADR when you make a decision that:

  • Affects more than one team or codebase area, OR
  • Locks in a choice that is expensive to reverse, OR
  • Will be questioned later by people who weren't in the room.

If a decision is reversible in an afternoon, you probably don't need an ADR. If it's a 3-month migration to undo, you do.

Template

# ADR-NNN: <title in present tense>

- **Status:** Proposed | Accepted | Superseded by ADR-XXX
- **Date:** YYYY-MM-DD
- **Deciders:** name(s) or role(s)

## Context
What problem are we solving? What constraints exist?

## Decision
What we decided to do. One paragraph, plain language.

## Alternatives considered
- **Option A** — why we didn't pick it
- **Option B** — why we didn't pick it

## Consequences
- Positive: what this gives us
- Negative: what this costs us
- Follow-ups: what we now need to do

How to add one

  1. Copy the template above to ADR-NNN-<short-kebab-title>.md (next number in sequence).
  2. Fill it in.
  3. Open a PR. ADRs get reviewed like code.
  4. Once merged, status becomes Accepted.
  5. If a later decision supersedes this one, update the status to Superseded by ADR-XXX and leave the old file in place — never delete ADRs.

Index

#TitleStatus
001Modular monolith + 3 satellite services from day 1Accepted
002packages/ over libs/; domain-first layoutAccepted
003Domain package shape — domain / application / infrastructure / presentationAccepted
004Frontend apps use <audience>-web namingAccepted
005Money is NUMERIC(20, 0) in santim; floats forbiddenAccepted
006The ledger is the sole source of money truth — no derived balance columnsAccepted
007Idempotency-Key required on every money-moving POSTAccepted
008Audit + outbox event live in the same DB transaction as the state changeAccepted
009No DELETE on financial rows — reversals onlyAccepted
010Two-language ceiling — TypeScript and Go onlyAccepted
011Cross-domain communication is event-only — no direct imports between domain packagesAccepted
012Ledger accounting model — double-entry, derived balances, DB-enforced invariantsAccepted
013Tenant isolation via Postgres RLS, fail-closed by defaultAccepted
014DemozPay is a bank-to-bank orchestrator, not a custodianAccepted (pending Legal counter-sign)
015Adjustment-journal process for non-zero reconciliation driftProposed
016Dispute workflow for contested money movementsProposed
017Postgres outbox + table-poller is the event-transport spine; no running brokerAccepted
018Progressive service extraction behind stable contractsAccepted
019Database-per-service — per-context schemas at MVP, separate DBs on extractionAccepted
020Contract-first integration — packages/contracts is the law (buf + schema registry + CI gates)Accepted
021Lightweight saga orchestration at MVP; Temporal deferredAccepted
022Kafka topic naming, ownership, versioning, DLQ & replay (when activated)Accepted
023API Gateway + BFF; REST only at the edgeAccepted
024Custody model — partner banks hold funds, the Ledger is the record, no internal walletAccepted (pending Legal counter-sign)
025Bank Gateway adapter contract — bank-sandbox is a dev adapter; real FIs plug into the same portAccepted
026Typed event contracts + Kafka consumer rulesAccepted
027Redis usage posture — cache & ephemeral state only, never a source of truthAccepted
028Unified compensation modelAccepted
029DemozAuth — framework-agnostic auth platformAccepted (Phase 2a)