Low Level Design - Design an ATM System
Requirements
- Authentication: Authenticate users using their card and ATM PIN.
- Balance Inquiry: Allow users to check their account balance.
- Cash Withdrawal: Enable users to withdraw cash.
- Cash Dispensing: Dispense cash in denominations of 500, 200, and 100 Rupees.
Class Diagram
Card
------
- id: String
- accountId: String
- cardNumber: String
- pin: EncryptedString (encrypted for security)
- expiryDate: LocalDate
--------------------------
+ validateCard() : boolean
+ isExpired() : boolean
Account
-------
- id: String
- accountHolder: String
- accountNumber: String
- totalBalance: BigDecimal
- availableBalance: BigDecimal
------------------------------
+ debitAmount(BigDecimal amount)
+ creditAmount(BigDecimal amount)
+ getAvailableBalance() : BigDecimal
+ isSufficientBalance(BigDecimal amount) : boolean
ATM
---
- availableBalance: Integer
- notesCountMap: Map<Note, Integer>
- currentATMState: ATMState
-----------------------------------
+ authenticateUser(String cardNumber, String pin) : boolean
+ updateATMBalance(BigDecimal amount)
+ dispenseCash(BigDecimal amount)
+ setCurrentState(ATMState state)
enum Note
FIVE_HUNDRED / TWO_HUNDRED / ONE_HUNDRED
enum TransactionType
CASH_WITHDRAWAL / CHECK_BALANCE
enum AccountType
SAVINGS / CURRENT
Design Choices
1. Factory Pattern
As there can be multiple Account Types, (SAVINGS / CURRENT / etc.,) we can use Factory Pattern to create different types of accounts adhering to the principle of creating objects without exposing the creation logic.
CurrentAccount extends Account {
...
...
}
SavingsAccount extends Account {
...
...
}
AccountCreationFactory {
public Account create(AccountType accountType) {
switch (accountType):
CURRENT:
return new CurrentAccount();
SAVINGS:
return new SavingsAccount();
default:
throw new Exception();
}
}
2. State Pattern
The State Pattern is employed to manage the different states of the ATM. Each state (IdleState
, CardInsertedState
, etc.) encapsulates the behavior associated with a particular stage of interaction with the ATM.
interface ATMState {
void insertCard();
void removeCard();
void selectOperation();
void checkBalance();
void withdrawCash();
}
class IdleState implements ATMState {
private ATM atm;
public void insertCard() {
// operation
atm.setCurrentState(new CardInsertedState(atm));
}
}
class CardInsertedState implements ATMState {
private ATM atm;
public void selectOperation(TransactionType transactionType) {
// operation
atm.setCurrentState(new CheckBalanceState(atm));
}
}
....
....
3. Chain of Responsibility Pattern
The Chain of Responsibility is utilized for dispensing cash. Each dispenser (FiveHundredNotesDispenser
, TwoHundredNotesDispenser
, and OneHundredNotesDispenser
) takes responsibility for dispensing a specific denomination of notes and passes the remaining amount to the next dispenser in the chain.
abstract class CashDispenser {
private CashDispenser nextDispenser;
public abstract void dispense(int amount);
}
class FiveHundredNotesDispenser extends CashDispenser {
@Override
public void dispense(int amount) {
if (amount <= 0) {
return;
}
int numNotes = amount / 500;
int remainder = amount % 500;
// operation to dispense numNotes
this.nextDispenser.dispense(remainder);
}
}
class TwoHundredNotesDispenser extends CashDispenser {
@Override
public void dispense(int amount) {
if (amount <= 0) {
return;
}
int numNotes = amount / 200;
int remainder = amount % 200;
// operation to dispense numNotes
this.nextDispenser.dispense(remainder);
}
}
....
....
Database Schema
Card
--------
id: varchar PK
accountId: varchar FK
cardNumber: String unique, index
pin: String
expiryDate: LocalDate
Account
----------
id: varchar PK
accountHolder: String
accountNumber: String unique, index
totalBalance: Integer
availableBalance: Integer
ATM
--------
id: varchar PK
location: String
notesCount: jsonb
atmState: String
Transaction
-----------
id: transactionId PK
accountId: FK
cardId: FK
amount: String
type: String
API Specifications
Authenticate User
- API:
POST /account/authenticate
- Request Body:
- cardNumber: String
- pin: String
Disburse Amount
- API:
POST /account/{accountId}/disburse
- Request Body:
- amount: Integer
Check Balance
- API:
POST /account/{accountId}/balance