A command-line ATM simulation system built with Clean Architecture principles, featuring account management, money transfers, and automatic debt tracking.
- Account Management: Login and logout functionality
- Financial Operations: Deposit, withdraw, and transfer money
- Automatic Debt Tracking: System automatically creates debts when transferring more than available balance
- Debt Forgiveness: When a creditor transfers to a debtor, the debt is reduced first
- Auto-Pay Debts: Deposits automatically pay off existing debts before adding to balance
- Interactive CLI: Real-time command-line interface for all operations
This project follows Clean Architecture (Hexagonal Architecture) principles with clear separation of concerns:
src/
├── domain/ # Enterprise Business Rules
│ ├── entities/ # Domain entities (Account, Debt)
│ └── repositories/ # Repository interfaces
├── application/ # Application Business Rules
│ ├── use-cases/ # Use case implementations
│ └── state/ # Application state management
├── interfaces/ # Interface Adapters
│ ├── controllers/ # Input adapters
│ └── presenters/ # Output adapters
└── infrastructure/ # Frameworks & Drivers
├── cli/ # CLI entry point
└── persistence/ # In-memory repository implementations
- Dependency Inversion: Inner layers don't depend on outer layers
- Domain Isolation: Business logic is independent of frameworks
- Repository Pattern: Abstract data persistence from business logic
- Use Case Driven: Each use case encapsulates a single business operation
- Test Independence: Each layer can be tested in isolation
- Node.js (v18 or higher recommended)
- npm
Start the CLI application:
npm startOr using the shell script:
bash start.shnpm start < input.txtOr:
bash start.sh < input.txtlogin <name>- Login with a user account (creates account if doesn't exist)deposit <amount>- Deposit money into your accountwithdraw <amount>- Withdraw money from your accounttransfer <name> <amount>- Transfer money to another userlogout- Logout from current sessionexit- Exit the application
$ login Alice
Hello, Alice!
Your balance is $0
$ deposit 100
Your balance is $100
$ logout
Goodbye, Alice!
$ login Bob
Hello, Bob!
Your balance is $0
$ deposit 80
Your balance is $80
$ transfer Alice 50
Transferred $50 to Alice
Your balance is $30
$ transfer Alice 100
Transferred $30 to Alice
Your balance is $0
Owed $70 to Alice
$ deposit 30
Transferred $30 to Alice
Your balance is $0
Owed $40 to Alice
$ logout
Goodbye, Bob!
$ login Alice
Hello, Alice!
Your balance is $210
Owed $40 from Bob
$ transfer Bob 30
Your balance is $210
Owed $10 from Bobnpm testOr using the shell script:
bash test.shnpm run test:watchnpm run test:coverageCoverage reports are generated in the coverage/ directory.
Run all E2E tests:
bash e2e-tests/run-all.shRun a single E2E test:
bash e2e-tests/run-single.sh test-cases/1See e2e-tests/README.md for more details.
- Normal Transfer: If sender has sufficient balance, transfer full amount
- Insufficient Balance: Transfer available cash and create debt for remainder
- Debt Forgiveness: If receiver owes sender, reduce debt first before transferring cash
- Auto-Pay Debts: Automatically pays off debts in order before adding to balance
- Priority Payment: Pays oldest debts first
- Chaining Payment: When a creditor receives payment, if they also have debts, the system automatically pays off their debts first (recursive chain payment)
- Remaining Balance: Any amount left after debt payment is added to account
- Debts are automatically created when transferring more than available balance
- Debts are automatically reduced when creditor transfers to debtor (debt forgiveness)
- Debts are automatically paid when depositing money
- Chaining Payment: When paying a debt, if the creditor also has debts, their debts are automatically paid first (recursive chain payment)
- Multiple debts to different creditors are supported
- Circular debt protection: Infinite loops are prevented using a visited set
dkatalis-atm/
├── src/
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── Account.js # Account entity
│ │ │ └── Debt.js # Debt entity
│ │ └── repositories/
│ │ ├── AccountRepository.js # Account repository interface
│ │ └── DebtRepository.js # Debt repository interface
│ ├── application/
│ │ ├── state/
│ │ │ └── SessionManager.js # Session state management
│ │ └── use-cases/
│ │ ├── LoginUser.js # Login use case
│ │ ├── LogoutUser.js # Logout use case
│ │ ├── DepositMoney.js # Deposit use case
│ │ ├── WithdrawMoney.js # Withdraw use case
│ │ └── TransferMoney.js # Transfer use case
│ ├── interfaces/
│ │ ├── controllers/
│ │ │ └── CliController.js # CLI command controller
│ │ └── presenters/
│ │ └── CliPresenter.js # CLI output presenter
│ ├── infrastructure/
│ │ ├── cli/
│ │ │ └── main.js # Application entry point
│ │ └── persistence/
│ │ ├── InMemoryAccountRepository.js
│ │ └── InMemoryDebtRepository.js
│ └── __tests__/ # Unit test files (mirrors src structure)
│ ├── domain/
│ ├── application/
│ ├── interfaces/
│ └── infrastructure/
├── e2e-tests/ # End-to-end tests
│ ├── test-cases/ # E2E test cases
│ ├── run-all.sh # Run all E2E tests
│ └── run-single.sh # Run single E2E test
├── input.txt # Sample input file
├── package.json
├── jest.config.js
├── start.sh # Start script
└── test.sh # Test script
- Define Entity (if needed): Add domain entities in
src/domain/entities/ - Create Use Case: Implement business logic in
src/application/use-cases/ - Update Controller: Add command handling in
src/interfaces/controllers/ - Update Presenter: Add output formatting in
src/interfaces/presenters/ - Write Tests: Add comprehensive tests in
src/__tests__/
- Domain Layer: Test pure business logic without mocks
- Application Layer: Test use cases with mocked repositories
- Interface Layer: Test presenters and controllers with mocked use cases
- Infrastructure Layer: Test repository implementations
- Follow Clean Architecture principles
- Maintain separation of concerns
- Write comprehensive tests (aim for 100% coverage)
- Use dependency injection
- Keep use cases focused and single-purpose
- Runtime: Node.js with ES Modules
- Testing: Jest (with experimental VM modules support)
- Repository Pattern: Abstracts data persistence
- Dependency Injection: Constructor-based injection
- Command Pattern: CLI commands
- Presenter Pattern: Output formatting
- Use Case Pattern: Business logic encapsulation
User Input → CliController → Use Case → Repository → Entity
↓ ↓
Presenter ← DTO ← Use Case ← Repository
↓
CLI Output
ISC
Tran Phuc Thanh (James)