Inkress ADRsarchitecture decisions
Overview / Ledger / money
ADR 0030

Money is stored as a plain numeric amount plus a currency code

AcceptedLedger / money

Context

Every monetary value needs an amount and a currency. Two broad shapes were available: a Money value object that binds amount+currency into one type enforced everywhere, or a plain numeric column with a sibling currency code. There is also the perennial amount-precision choice: integer minor units (cents) vs a decimal/float in major units.

Decision

Store money at rest as a plain database numeric amount alongside a currency_code string, in major units, with no Money type in the schema — though the fee/currency calculators do use the ex_money Money value object during computation.

  • Amount + currency columns. An amount is an ordinary numeric column; the currency is a sibling currency_code (ISO code) on the same row. Pairing is by convention and by the currency-partitioned account model (ADR 0032), not by a wrapping type.
  • Major units, not minor-unit integers. Amounts are stored in major units (e.g. dollars), not integer cents.
  • Precision: float on the legacy path, decimal on the new ledger. The capture-path ledger historically uses :float; the double-entry projection (ADR 0029) uses :decimal to remove float rounding error going forward.

Consequences

  • Positive: amounts are simple, directly queryable/aggregatable columns; the schema needs no Money-type serialization (the calculators convert to ex_money Money where currency-safe arithmetic helps, then back to a decimal for storage).
  • Neutral / to revisit: without a Money type, "an amount always travels with its currency" is a discipline, not a compiler guarantee, and nothing structurally prevents mixing currencies in a careless calculation (the currency-partitioned accounts of ADR 0032 are the real guard).
  • Neutral / to revisit: major-units :float on the legacy path is prone to rounding; the new ledger's :decimal fixes this, but a float→decimal boundary exists while both ledgers run (ADR 0029).

Alternatives considered

  • A Money value object as the storage representation: not adopted at rest — columns stay plain numeric + currency_code; ex_money's Money is used inside the calculators, not in the schema, so the pairing is enforced there and by per-currency accounts (ADR 0032) rather than by the stored type.
  • Integer minor units (cents): not adopted for storage — amounts are kept in major units; precision is instead addressed by moving the authoritative ledger to :decimal. (Minor-unit integers remain the textbook alternative if the float/decimal split ever proves troublesome.)
← 0029 The double-entry ledger is an async, rebuildable projection of the capture-path ledger 0031 Balances are per-currency wallet accounts; available balance subtracts reservations at compute time →