Requirements & System Scope
Functional Scope (In-Scope)
- Atomic Money Transfers: Perform money transactions securely ensuring complete atomic credit and debit steps.
- Zero Floating-Point Errors: Use arbitrary-precision decimals (BigDecimal) to avoid float calculation flaws.
- Strict Idempotency: Check idempotency keys before transaction processing to block duplicates.
- Command Pattern for Rollbacks: Encapsulate operations inside Command objects, supporting robust undo/rollback mechanisms.
- Append-Only Event Ledger: Maintain immutable transactional histories.
Explicit Boundaries (Out-of-Scope)
- No Real Bank Card Integration: Bypasses live Visa/Mastercard processing flows or clearinghouses.
- No Multi-Currency FX Engine: Overwrite parameters do not calculate real-time FX exchange conversion spreads.
Class Diagram & Entity Relationships
Structural layout showing relationships between wallets, transaction commands, and the wallet coordinator service:
Loading...
- Command Pattern Representation: Transactions are modeled as command objects, maintaining internal states (PENDING, SUCCESS, FAILED, ROLLED_BACK) and referencing target wallets directly.
- Atomic Account Protection: Wallets contain individual ReentrantLocks to support high-performance thread safety.
Design Patterns & SOLID Principles
- Command Pattern (Transaction Orchestration): Decoupling execution from transaction requests. The
TransactionCommandinterface structures actions cleanly, keeping business logic (P2P transfers, deposits, withdrawals) separate from the core service class. It also enables self-contained rollback behaviors. - Deterministic Lock Sorting Pattern (Deadlock Prevention): Instead of locking source then destination (which triggers deadlocks if two parties pay each other simultaneously), locks are acquired in sorted string order of their unique wallet IDs.
- Single Responsibility Principle (SRP):
Wallethandles balance adjustments;TransactionCommandmanages the concrete atomic transfer execution;WalletServicemanages registration, ledger auditing, and rollback triggers.
Core Execution Workflows
P2P Transfer Sequence
- Sender requests transfer:
executeTransfer(idempotencyKey, srcId, destId, amount). - Verify transaction registry:
- If key exists, return cached transaction record immediately.
- Sort wallet IDs alphabetically to avoid deadlock.
- Acquire first lock, then acquire second lock:
- Verify source wallet balance has sufficient funds.
- Deduct funds from source and credit destination wallet.
- Set transaction status to SUCCESS.
- On failure, set status to FAILED (rollback state is preserved naturally as balances are not modified).
- Write command to append-only ledger list, release locks, and return transaction details.
Concurrency & Thread Safety Strategy
The critical concurrency vulnerability occurs when multiple transfer threads attempt to read and write to the same account:
- Deterministic Sorted Locks: Standardizing the lock order by wallet ID prevents cyclical dependency deadlocks.
- Concurrent HashMaps: Store active wallets and idempotency logs in concurrent structures to prevent data corruption.
- Fine-Grained Reentrant Locks: Account-level locks maximize transaction throughput rather than throttling at service-wide levels.
Complete Clean Code Blueprint
Thread-safe, command-pattern-driven financial transfer blueprints in Java and Python:
// โโโ JAVA BLUEPRINT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
enum TransactionStatus { PENDING, SUCCESS, FAILED, ROLLED_BACK }
interface TransactionCommand {
boolean execute();
void undo();
String getId();
TransactionStatus getStatus();
BigDecimal getAmount();
}
class Wallet {
private final String id;
private BigDecimal balance;
private final ReentrantLock lock = new ReentrantLock();
public Wallet(String id, BigDecimal initialBalance) {
this.id = id;
this.balance = initialBalance;
}
public String getId() { return id; }
public BigDecimal getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
public ReentrantLock getLock() { return lock; }
public boolean debit(BigDecimal amount) {
if (balance.compareTo(amount) >= 0) {
balance = balance.subtract(amount);
return true;
}
return false;
}
public void credit(BigDecimal amount) {
balance = balance.add(amount);
}
}
class P2PTransferCommand implements TransactionCommand {
private final String id;
private final Wallet source;
private final Wallet destination;
private final BigDecimal amount;
private TransactionStatus status;
public P2PTransferCommand(String id, Wallet source, Wallet destination, BigDecimal amount) {
this.id = id;
this.source = source;
this.destination = destination;
this.amount = amount;
this.status = TransactionStatus.PENDING;
}
@Override
public boolean execute() {
// Sort locks alphabetically by wallet ID to ensure deadlock immunity
Wallet first = source.getId().compareTo(destination.getId()) < 0 ? source : destination;
Wallet second = source.getId().compareTo(destination.getId()) < 0 ? destination : source;
first.getLock().lock();
try {
second.getLock().lock();
try {
if (source.debit(amount)) {
destination.credit(amount);
status = TransactionStatus.SUCCESS;
return true;
} else {
status = TransactionStatus.FAILED;
return false;
}
} finally {
second.getLock().unlock();
}
} finally {
first.getLock().unlock();
}
}
@Override
public void undo() {
if (status != TransactionStatus.SUCCESS) return;
Wallet first = source.getId().compareTo(destination.getId()) < 0 ? source : destination;
Wallet second = source.getId().compareTo(destination.getId()) < 0 ? destination : source;
first.getLock().lock();
try {
second.getLock().lock();
try {
if (destination.debit(amount)) {
source.credit(amount);
status = TransactionStatus.ROLLED_BACK;
}
} finally {
second.getLock().unlock();
}
} finally {
first.getLock().unlock();
}
}
public String getId() { return id; }
public TransactionStatus getStatus() { return status; }
public BigDecimal getAmount() { return amount; }
}
class WalletService {
private final Map<String, Wallet> wallets = new ConcurrentHashMap<>();
private final Map<String, TransactionCommand> idempotencyStore = new ConcurrentHashMap<>();
private final List<TransactionCommand> ledger = new CopyOnWriteArrayList<>();
public void registerWallet(Wallet wallet) {
wallets.put(wallet.getId(), wallet);
}
public Wallet getWallet(String id) {
return wallets.get(id);
}
public TransactionCommand executeTransfer(String idempotencyKey, String srcId, String destId, BigDecimal amount) {
if (idempotencyStore.containsKey(idempotencyKey)) {
System.out.println("[Service] Returning cached transaction for idempotency key: " + idempotencyKey);
return idempotencyStore.get(idempotencyKey);
}
Wallet src = wallets.get(srcId);
Wallet dest = wallets.get(destId);
if (src == null || dest == null) {
throw new IllegalArgumentException("Invalid wallets involved in transfer");
}
String txnId = "TXN-" + UUID.randomUUID().toString().substring(0, 8);
TransactionCommand command = new P2PTransferCommand(txnId, src, dest, amount);
command.execute();
ledger.add(command);
idempotencyStore.put(idempotencyKey, command);
return command;
}
public void rollbackTransaction(String txnId) {
for (TransactionCommand cmd : ledger) {
if (cmd.getId().equals(txnId)) {
cmd.undo();
System.out.println("[Service] Successfully rolled back Transaction: " + txnId);
return;
}
}
System.out.println("[Service] Transaction not found for rollback: " + txnId);
}
public List<TransactionCommand> getLedger() {
return Collections.unmodifiableList(ledger);
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Digital Wallet Concurrent Simulation ===");
WalletService service = new WalletService();
Wallet w1 = new Wallet("ACC-01", new BigDecimal("1000.00"));
Wallet w2 = new Wallet("ACC-02", new BigDecimal("500.00"));
service.registerWallet(w1);
service.registerWallet(w2);
ExecutorService executor = Executors.newFixedThreadPool(2);
// Concurrent overlapping payments between ACC-01 and ACC-02 (locks are ordered, preventing deadlocks)
executor.submit(() -> {
service.executeTransfer("KEY-101", "ACC-01", "ACC-02", new BigDecimal("200.00"));
});
executor.submit(() -> {
service.executeTransfer("KEY-102", "ACC-02", "ACC-01", new BigDecimal("100.00"));
});
executor.shutdown();
executor.awaitTermination(2, TimeUnit.SECONDS);
System.out.println("ACC-01 Balance: " + w1.getBalance());
System.out.println("ACC-02 Balance: " + w2.getBalance());
// Perform a rollback simulation
TransactionCommand lastTxn = service.getLedger().get(0);
System.out.println("\nRolling back transaction: " + lastTxn.getId() + " of amount: " + lastTxn.getAmount());
service.rollbackTransaction(lastTxn.getId());
System.out.println("ACC-01 Balance after rollback: " + w1.getBalance());
System.out.println("ACC-02 Balance after rollback: " + w2.getBalance());
}
}
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...