Capstone 4 — Banking System¶
Scope: a term's work · Prerequisites: Modules 1–15
1. Problem statement¶
A small credit union needs a system of record for its accounts and transactions.
The requirements look simple and are not. Money must never be inexact, so every amount is integer pence. Every balance change must have a matching entry in a ledger, and the ledger must agree with the balances at all times — including after a failure halfway through an operation. Transfers between accounts must either happen completely or not at all, and the system must be able to answer, for any account and any date, what the balance was on that date.
The system is stopped and restarted daily, and its data file has been edited by hand at least once in the past.
2. Actors¶
| Actor | What they do | What the system owes them |
|---|---|---|
| Teller | Opens accounts, deposits, withdraws, transfers | Operations that either complete or leave nothing behind, and a receipt |
| Customer | Checks a balance, lists transactions, requests a statement | The same balance every time it is asked, and a statement that reconciles |
| Auditor | Reconciles the ledger against the balances | An append-only ledger, and the ability to reconstruct any balance |
3. Use cases¶
- Open an account, in one of several kinds (current, savings, fixed-term).
- Deposit and withdraw, with the balance never going negative where the account forbids it.
- Transfer between two accounts, atomically — either both entries exist or neither does.
- Compute interest, at a rate that depends on the account kind and the balance band.
- Apply a charge that depends on the account kind (a fixed-term withdrawal fee, for example).
- List transactions for an account, and compute the balance on a given date.
- Produce a statement for a period, showing an opening balance, entries and a closing balance.
- Refuse an operation and say why: insufficient funds, account closed, limit exceeded.
- Save and load everything; refuse a corrupted file with a line number and no partial state.
4. Suggested classes¶
| Class | Responsibility, in one sentence | Notes |
|---|---|---|
Money |
An exact amount in pence, with the arithmetic that is allowed | A value type: no double, no rounding errors |
AccountKind |
What differs between kinds: overdraft allowed, interest rate, withdrawal fee | An interface or a value used by policies |
Account |
One account's identity and current balance | Exposes operations, never a settable balance |
Ledger |
The append-only list of entries, and the source of truth | Owns its entries |
Entry |
One dated posting against one account, with a running balance | Immutable once constructed |
Transaction |
One operation that may post several entries, atomically | The unit of the "all or nothing" rule |
InterestPolicy |
Turns a balance and an account kind into interest | Injected; the thing that varies |
FeePolicy |
Turns an operation and an account kind into a charge | Injected |
Statement |
A rendered period for one account | A projection, computed from the ledger |
Bank |
Coordinates operations and owns accounts and the ledger | Where atomicity is enforced |
The class worth the most marks is Transaction. Atomicity in a single-threaded C++ program means:
build the entries, validate everything, and only then apply them — so that an exception during validation
leaves nothing behind. The design has to make "apply" a single step that cannot fail, and the test that
proves it passes an operation that fails mid-way and shows the ledger unchanged.
5. Relationships¶
Bank ◆── vector<Account> composition
Bank ◆── Ledger composition: one ledger for the institution
Ledger ◆── vector<Entry> composition, and append-only
Entry ────> Account association: an entry refers to an account it does not own
Account ┈┈> AccountKind dependency
Bank ┈┈> InterestPolicy dependency, injected
Bank ┈┈> FeePolicy dependency, injected
Transaction ◆── vector<Entry> composition: the entries a transaction will post
Money <value> value type; no identity, no inheritance
6. Design constraints¶
- No
doubleanywhere in the domain. Report why, and what the alternative in your design is. - Balances are derived from the ledger, or maintained alongside it with the ledger as truth. Choose one and say which; two independently maintained answers is the invariant bug this project exists to prevent.
- Atomicity is enforced in one place: validate fully, then apply in a step that cannot fail. An operation that fails must leave both the ledger and the balances untouched — demonstrated by a test.
- Interest and fees are policies, injected, so a rate change is a value change rather than a code change.
- The ledger is append-only. No correction edits an entry; a reversal is a new entry, and both remain visible.
- A balance on a past date is a query over the ledger, and you must say what it costs in your data structures.
- Format: a documented text format with a version marker and an escape rule; amounts stored as integers; a file whose marker is from a later version refused, not misread.
7. Milestones¶
| # | Milestone | Done when |
|---|---|---|
| 1 | Design | Diagram; the ledger/balance relationship decided and justified; the atomicity rule located in exactly one place |
| 2 | Money and accounts | Money usable and tested; accounts with kinds; deposits and withdrawals with refusals |
| 3 | Ledger and atomicity | Every operation posts entries; a failed operation leaves nothing behind, demonstrated |
| 4 | Policies | Interest and fees from injected policies, with two implementations each |
| 5 | Persistence | Documented format; round trip preserving every entry including an empty ledger; three hand-edited corruptions rejected with line numbers |
| 6 | Tests and report | Ledger and balances agree under a scripted sequence; a deliberate bug caught; the reversal named with its cost |
8. Minimum requirements¶
- [ ] Money as an exact integer type, with the arithmetic the domain needs and no
double - [ ] At least two account kinds, differing in overdraft permission, interest and fees
- [ ] Deposit, withdraw and transfer, each refusing invalid requests with a reason
- [ ] An append-only ledger, with every balance change matched by an entry
- [ ] Atomic transfers: a transfer that fails leaves no entry and no balance change — demonstrated
- [ ] Interest and fee policies injected, with two implementations each
- [ ] A statement for a period, with an opening and closing balance that reconciles with the entries
- [ ] Persistence with a version marker and escape rule; a round trip preserving every entry, including an empty ledger
- [ ] Three hand-edited corruptions rejected with line numbers, leaving the loaded state untouched
- [ ] At least two patterns, each justified by a named requirement; one declined, with the reason
- [ ] Tests with assertions covering invariants, boundaries and failure paths
- [ ] No warnings with
-Wall -Wextra -Werror
9. Advanced requirements¶
- [ ] A balance on any past date, with the complexity of the query stated and measured
- [ ] Account closure that forbids further postings but keeps the history visible
- [ ] Daily interest posting for a whole book, with a check that the total matches the sum of the individual postings
- [ ] Overdraft limits per account kind, refused at the boundary, with the refusal naming the limit
- [ ] A third account kind added, with the file count in the report
- [ ] A reconciliation command that recomputes every balance from the ledger and reports mismatches — which is also the strongest test of your invariants
- [ ] An audit export of the ledger to CSV behind the same interface as the console output
10. Testing requirements¶
- Invariants: every balance equals the ledger's effect on that account; the sum of all postings is
zero for a transfer; no
doubleappears in the domain; an entry is never mutated - Boundaries: withdrawing exactly the balance; one penny over an overdraft limit; a zero-amount operation; a transfer between the same account twice; a date query exactly at midnight of a posting
- Failure paths: insufficient funds; a transfer where the second posting is invalid; loading a truncated file; a corrupted amount; a negative interest rate
- Round trip: populated and empty ledgers, every entry compared, including a name containing the separator, and the order of entries preserved
- One deliberate bug (for example, applying the first posting before validating the second), caught by the suite, with the failing assertion kept
11. Extension ideas¶
- Scheduled payments, with a rule for what happens on a day the system is not running.
- Statement export in a second format, behind the same interface.
- Interest tiers by balance band, added as a policy change with no edits to existing code — count the files.
- A simple budget: postings tagged by category, summarised per period.
- Multiple currencies, and the design decision that follows about
Money(which the report should discuss, including why it is not in the minimum requirements). - A reconciliation report that runs on demand and prints the number of entries checked.
12. Evaluation checklist¶
- [ ] The diagram matches the declarations, including multiplicities
- [ ]
grepfordoublein the domain finds nothing — and the report says why - [ ] One source of truth for balances is named, and the other is either derived or proven consistent
- [ ] A failed operation left the ledger and balances untouched — demonstrated by a test
- [ ] The ledger is append-only; a correction is a new entry, and both are visible
- [ ] Interest and fees come from injected policies, and the rate change needed no code edit
- [ ] A statement reconciles: opening + entries = closing, checked by the program, not by hand
- [ ] The round trip preserves every entry and its order, including an empty ledger — output pasted
- [ ] Three corruptions rejected with line numbers, leaving the loaded state untouched
- [ ] The suite fails when a deliberate bug is introduced
- [ ] One pattern considered and declined, in writing
- [ ] The report names the decision I would reverse and the cost of reversing it
Related: Capstone options · Projects and the rubric · Module 16 — Capstone · Module 10 — Copying and Resource Management