Balances Are Derived, Not Stored
A sample article. Why Ledgerly keeps an append-only ledger and computes every balance from it — and what that bought in correctness.
Another sample post to show the layout; swap it for something you have actually built.
The first version of Ledgerly stored a balance on each user. It was fast and simple, and it drifted: a retried request here, a half-finished settlement there, and soon the numbers no longer added up to zero, which in a shared-expense app is the one thing they must do.
The second version stores only facts. Every expense and every payment is a row in an append-only ledger, and a balance is simply the sum of the rows that mention you. There is nothing to drift, because there is nothing to keep in sync. If two sums disagree, one of them is wrong in a way you can find.
Idempotency came along for free once there was a ledger. Every write carries a client-generated key, and a unique index on that key means a double-tap, a flaky connection and an impatient retry all collapse into one row. The settlement screen can be as eager as it likes.
The cost is that reads do more work. For a household of six this is irrelevant; for a trip of sixty with a year of history it is not, so there is a materialised summary that is rebuilt from the ledger and can always be thrown away and rebuilt. The ledger is the truth; everything else is a cache.
The lesson is general. When you can choose between storing a conclusion and storing the evidence for it, store the evidence.