A banking system: money is moved, never just changed
Published . By the DiagramDesk team.
The first design most people draw for a bank has an Account with a balance, and a transfer that subtracts from one balance and adds to another. It works until anyone asks what happened: where the 250 went on Tuesday, or why two numbers disagree. A bank's model is built round the record of movements, and the balance comes second.
The ledgerA transferClassesUse casesData flowMistakes
Accounts, holders and a ledger of entries
Three decisions shape the ER model. The first is that an account can have more than one holder. A joint account belongs to two customers, and one customer often holds several accounts, so the two are many-to-many, resolved by Account holder with a role saying whether this customer is a sole or joint holder. Its key is the customer and the account together, so both of its lines are solid, identifying relationships. So is the line to Loan, which is keyed by the account it belongs to; the other lines are dashed, because Account and Entry each have a key of their own.
The second is the ledger. Every movement of money is a Transfer made of Entries, each of which adds to or takes from one account. Moving 250 from savings to current is one transfer with two entries, minus 250 on savings and plus 250 on current, and they sum to zero. That is double-entry bookkeeping reduced to its core, and it is what lets anyone reconstruct any balance on any day by adding up the entries.
The third decision is what to do with the balance. It could be left out entirely and worked out from the entries every time, which is correct but slow for an account with years of history. So the model keeps it, with a note that it is kept in step with the entries: it is a stored copy of a sum, updated in the same unit of work as the entries that change it, and if the two ever disagree the entries win. Saying that in one sentence in a report is worth more than either choice made silently.
Account numbers are stored as text, not as numbers. Nobody adds two account numbers together, and a number type would drop a leading zero.
A transfer, all or nothing
The sequence diagram for a transfer between two of the customer's own accounts shows the rule that matters most in banking software: both entries are written, or neither is. The transfer service opens one unit of work (a database transaction), locks both accounts so no other transfer can change them halfway, checks the money is there, writes the two entries and commits. If anything fails, it rolls back, and the ledger looks as if nothing was attempted.
The lock is what a first attempt leaves out, and it is easy to justify in a write-up. Without it, two transfers from the same savings account at the same moment could each read 900, each decide there is enough, and together take out more than was there. The alt frame's second half is not an error screen afterthought: a refused transfer is a normal outcome and belongs in the diagram.
Classes: kinds of account
Savings and current accounts differ in one rule, whether the account can pay out a given amount. A savings account cannot go below zero; a current account can, down to its overdraft limit. That is the classic case for an abstract method: Account declares canPay(amount), and each subclass answers it its own way.
Transfer.isBalanced() is the class-diagram form of the double-entry rule, a check that the entries sum to zero before anything is saved. The multiplicity on Customer to Account is 1..* at the customer end: every account has at least one holder, and a joint account has more.
Use cases
A customer moves money between their own accounts, pays people at other banks and reads statements. A teller opens accounts and handles cash over the counter. A loan officer approves loans. Other banks appear as one secondary actor, because a payment to someone else's bank leaves this system and has to be sent through the channels banks use between themselves.
Transfer between own accounts and Pay someone at another bank are separate use cases even though both move money, because only one of them involves another bank and the steps differ. Checking identity documents is «include»d in opening an account because a bank cannot skip it, which is also why it is drawn as its own use case: it appears in the requirements as a named step.
Data flow: entries in, statements out
The level 1 DFD has one feature worth pointing out to a marker. Process 2.0, adding monthly interest, has no outside entity feeding it: it is started by the calendar and reads rates and balances from D1. That is allowed. A process needs data in and data out, and a store can be its only source.
Interest is written as entries, like every other movement, which is why the statement process reads D2 and nothing else. Like a transfer, process 2.0 also writes the new balances back to D1, because the model keeps each stored balance in step with its entries. A statement is a list of entries for one account over one month, and a model in which interest changed the balance directly would have a gap in every statement.
The mistakes to avoid
- A balance with no history. Without entries there is no statement and no way to explain a number.
- A transfer that updates two balances without one unit of work, so a failure halfway loses money.
- One customer per account, which rules out the joint account every bank offers.
- A single Transaction row with "from" and "to" columns, which cannot describe a movement with three parts, such as a payment with a fee.
- An account type field and switch statements, instead of subclasses answering
canPay. - Account numbers stored as numbers.
If your brief is for a cash machine rather than the bank behind it, the ATM example covers the machine's side of the same conversation.
Keep going
- The ATM system, the machine that asks this bank for permission
- All the worked systems
- Draw your own ER diagram