Back to projects

Case Study · Backend Product Design

FairShare

A ledger-first expense tracker that prioritizes explainable balances, deterministic money handling, and safe retries for financially sensitive writes.

Most expense apps stop at balances. I wanted a system that could explain every balance, preserve history, and avoid corrupting money movement when clients retry requests.

Money handling

Deterministic scale-2 rounding with stable leftover-cent assignment

Safety

Idempotent expense creation and settlement confirmation

Auditability

Expense lifecycle events and confirmed transfers retained for explanation and history

What I built

Built the product architecture across a Spring Boot backend and Next.js frontend, with the backend centered on ledger effects, settlement flows, and explainability.

Java 21Spring BootPostgreSQLFlywayNext.jsTypeScript
The backend stores groups, expenses, participant shares, ledger entries, confirmed transfers, and a thin event trail rather than treating balances as mutable standalone values.
The frontend handles group workflows, ledger views, and history screens while the backend owns accounting correctness, transactional writes, and explanation endpoints.
Schema changes flow through Flyway and the local system runs against PostgreSQL, with H2 used for automated tests.

Results

Built a balance model that can answer not just 'who owes whom' but also 'why does this number exist?' by tracing expenses and transfers behind each user position.
Kept the event history narrow enough to stay debuggable without committing the system to full event sourcing complexity.
Verified the financially sensitive paths with integration tests around settlement confirmation, events, pagination, and auth policy.

Hard problems

Deterministic rounding and leftover cents

Equal, percentage, and share-based splits all leave residue at scale 2. The system normalizes amounts, makes rounding explicit, and assigns leftover cents in a stable order so retries and updates do not reshuffle money between users.

Safe retries on financially sensitive writes

Settlement confirmation and expense creation needed idempotency because duplicate writes immediately corrupt balances. I added explicit idempotency keys and confirmation IDs only where the correctness risk justified the complexity.

Editing expenses without ledger drift

Expense updates recalculate target shares, compute deltas against prior state, and adjust ledger effects transactionally. The service takes a pessimistic lock on the expense row to serialize concurrent edits safely.

Reliability and verification

Used Flyway migrations with Hibernate validation so schema drift fails fast rather than being silently patched at startup.
Backed the main correctness paths with backend tests, including settlement confirmation, events and transfers, and pagination behavior.
Chose a monolithic backend intentionally to keep accounting consistency inside straightforward transactions rather than spreading it across services too early.

Tradeoffs and lessons

A monolith reduced domain isolation but simplified correctness for tightly coupled accounting logic.
The system records event history for auditability, but stops short of full event sourcing to avoid unnecessary replay complexity.
Caching was intentionally skipped in v1 to avoid invalidation bugs around edits, voids, and transfers before read latency became a proven issue.