Skip to content
← All patterns

fintech

Account Ledger

What moved through this account?

Money moved between two accounts, or not moved at all.

A transfer is two changes that must both happen: one balance down, one up, and a pair of ledger entries recording why. DynamoDB has no rollback, so the only way to make that safe is to send it as one transaction and let the database refuse the whole thing.

The model

Balance

What an account currently holds, in minor units.

pk
ACCOUNT#<accountId>
sk
BALANCE

Attributes: pence (N), currency (S), updatedAt (N)

LedgerEntry

One immutable line: which account, which direction, how much.

pk
ACCOUNT#<accountId>
sk
ENTRY#<sequence>

Attributes: pence (N), direction (S), transferId (S), at (N)

Access patterns

  • QueryRead an account

    The balance and the entries that produced it, in one read.

Design notes

There is no rollback to fall back onwhy

Write the debit, then fail before the credit, and the money is gone from one account and arrived nowhere. Nothing undoes it - DynamoDB has no transaction to abort after the fact, so compensating for a half-applied transfer means writing the reversal yourself and hoping that write succeeds. TransactWriteItems avoids the situation rather than recovering from it.

The guard rides along in the same transactionwhy

The debit carries a condition that the balance is at least the amount, so an overdraft is refused as part of the same all-or-nothing write. Checking the balance first would reopen the gap the transaction closes: between the check and the transfer, another transfer can spend the same money.

Balance and entries in one partitiontrade-off

BALANCE sorts before every ENTRY# for an account, so a statement is one Query returning the current figure followed by the history. The entries are the record of truth and the balance is a running total kept beside them - which is why a transfer writes both rather than deriving one from the other on read.

This one the engine genuinely cannot runtrade-off

Unlike most designs here, this is not the resolver's limitation. The wasm engine does not implement TransactWriteItems at all - it answers 'not supported' - so the transfer is not offered as a run at all rather than offered and failing. Every access pattern this page lists is one you can actually press. The transfer itself is four items in one write: debit, credit, and a ledger entry each way.

Taught in the course

  • Claim it, or fail - A ConditionExpression lets a write happen only if the data still allows it.
account-ledgerDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

The balance and the entries that produced it, in one read.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
PK(pk)
SK(sk)
currency
pence
updatedAt
at
direction
transferId
ACCOUNT#a-1044
BALANCES
GBPS
182450N
1790017920N
—
—
—
ACCOUNT#a-1044
ENTRY#000003S
—
60000N
—
1789257600N
creditS
tr-87004S
ACCOUNT#a-1044
ENTRY#000004S
—
2500N
—
1790017920N
debitS
tr-88120S
ACCOUNT#a-2071
BALANCES
GBPS
9930N
1790017920N
—
—
—
ACCOUNT#a-2071
ENTRY#000010S
—
60000N
—
1789257600N
debitS
tr-87004S
ACCOUNT#a-2071
ENTRY#000011S
—
2500N
—
1790017920N
creditS
tr-88120S
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.