Software Development Engineer

Blog PostsResume

Low Level Design - Design an ATM System

Requirements

  1. Authentication: Authenticate users using their card and ATM PIN.
  2. Balance Inquiry: Allow users to check their account balance.
  3. Cash Withdrawal: Enable users to withdraw cash.
  4. 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.

atm factory

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.

ATM State

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

© 2024 Ujjwal Bhardwaj. All Rights Reserved.