Pul Transfer Sistem Dizaynı
Problem Təsviri
İstifadəçilərə hesablar arasında pul transfer etməyə imkan verən, tranzaksiya tutarlılığını, təhlükəsizliyi və concurrent əməliyyatların düzgün işlənməsini təmin edən pul transfer sistemi dizayn edin.
Tələblər
-
Funksional Tələblər:
- İstifadəçi hesablarının yaradılması və idarə edilməsi
- Müxtəlif hesab növləri dəstəyi (məsələn, Checking, Savings)
- Hesablar arasında pul transferi
- Tranzaksiya tarixçəsinin görüntülənməsi
- Müxtəlif valyutalar və məzənnə dəstəyi
- İstifadəçilərə tranzaksiya statusu bildirişləri
- Tranzaksiya uğursuzluqları və rollback-lərin idarəsi
-
Qeyri-Funksional Tələblər:
- Təhlükəsizlik (autentifikasiya, avtorizasiya)
- Tranzaksiya tutarlılığı (ACID xassələri)
- Concurrent əməliyyatlar üçün thread safety
- Yüksək availability və performans
- Audit və logging imkanları
Əsas Komponentlər
- Hesab İdarəetməsi: İstifadəçi hesablarının yaradılması və saxlanması
- Transfer Servisi: Pul transfer əməliyyatlarının icrası
- Tranzaksiya Meneceri: ACID xassələrini təmin edir
- Bildiriş Sistemi: İstifadəçilərə tranzaksiya statusu bildirir
- Audit Sistemi: Bütün əməliyyatları qeyd edir
Dizayn Yanaşması
Müxtəlif transfer strategiyalarını dəstəkləmək üçün Strategy Pattern istifadə edəcəyik:
- Daxili Transfer: Eyni bank daxilində transfer
- Xarici Transfer: Müxtəlif banklar arasında transfer
- Valyuta Çevirməsi: Müxtəlif valyutalar arasında transfer
İcra
Koda bax
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentHashMap;
// Currency enum
enum Currency {
USD, EUR, AZN, GBP, TRY
}
// Transaction status enum
enum TransactionStatus {
PENDING, SUCCESS, FAILED, CANCELLED
}
// Account types
enum AccountType {
CHECKING, SAVINGS, BUSINESS
}
// User class
class User {
private final String userId;
private final String name;
private final String email;
private final List<String> accountNumbers;
public User(String userId, String name, String email) {
this.userId = userId;
this.name = name;
this.email = email;
this.accountNumbers = new ArrayList<>();
}
public String getUserId() { return userId; }
public String getName() { return name; }
public String getEmail() { return email; }
public List<String> getAccountNumbers() { return new ArrayList<>(accountNumbers); }
public void addAccount(String accountNumber) {
accountNumbers.add(accountNumber);
}
}
// Account class
class Account {
private final String accountNumber;
private final String userId;
private final AccountType accountType;
private final Currency currency;
private BigDecimal balance;
private final List<Transaction> transactionHistory;
private final Lock lock;
private boolean isActive;
public Account(String accountNumber, String userId, AccountType accountType,
Currency currency, BigDecimal initialBalance) {
this.accountNumber = accountNumber;
this.userId = userId;
this.accountType = accountType;
this.currency = currency;
this.balance = initialBalance;
this.transactionHistory = new ArrayList<>();
this.lock = new ReentrantLock();
this.isActive = true;
}
public String getAccountNumber() { return accountNumber; }
public String getUserId() { return userId; }
public AccountType getAccountType() { return accountType; }
public Currency getCurrency() { return currency; }
public boolean isActive() { return isActive; }
public BigDecimal getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
public boolean debit(BigDecimal amount, String transactionId, String description) {
lock.lock();
try {
if (!isActive || amount.compareTo(BigDecimal.ZERO) <= 0 || balance.compareTo(amount) < 0) {
return false;
}
balance = balance.subtract(amount);
transactionHistory.add(new Transaction(transactionId, accountNumber, null,
amount, currency, TransactionType.DEBIT,
description, LocalDateTime.now()));
return true;
} finally {
lock.unlock();
}
}
public void credit(BigDecimal amount, String transactionId, String description) {
lock.lock();
try {
if (isActive && amount.compareTo(BigDecimal.ZERO) > 0) {
balance = balance.add(amount);
transactionHistory.add(new Transaction(transactionId, null, accountNumber,
amount, currency, TransactionType.CREDIT,
description, LocalDateTime.now()));
}
} finally {
lock.unlock();
}
}
public void reverseTransaction(String transactionId) {
lock.lock();
try {
for (Transaction transaction : transactionHistory) {
if (transaction.getTransactionId().equals(transactionId)) {
if (transaction.getType() == TransactionType.DEBIT) {
balance = balance.add(transaction.getAmount());
} else if (transaction.getType() == TransactionType.CREDIT) {
balance = balance.subtract(transaction.getAmount());
}
transaction.setStatus(TransactionStatus.CANCELLED);
break;
}
}
} finally {
lock.unlock();
}
}
public List<Transaction> getTransactionHistory(int limit) {
lock.lock();
try {
int size = transactionHistory.size();
return new ArrayList<>(transactionHistory.subList(Math.max(0, size - limit), size));
} finally {
lock.unlock();
}
}
public void deactivate() {
lock.lock();
try {
isActive = false;
} finally {
lock.unlock();
}
}
}
// Transaction types
enum TransactionType {
DEBIT, CREDIT, TRANSFER
}
// Transaction class
class Transaction {
private final String transactionId;
private final String fromAccountNumber;
private final String toAccountNumber;
private final BigDecimal amount;
private final Currency currency;
private final TransactionType type;
private final String description;
private final LocalDateTime timestamp;
private TransactionStatus status;
public Transaction(String transactionId, String fromAccountNumber, String toAccountNumber,
BigDecimal amount, Currency currency, TransactionType type,
String description, LocalDateTime timestamp) {
this.transactionId = transactionId;
this.fromAccountNumber = fromAccountNumber;
this.toAccountNumber = toAccountNumber;
this.amount = amount;
this.currency = currency;
this.type = type;
this.description = description;
this.timestamp = timestamp;
this.status = TransactionStatus.PENDING;
}
public String getTransactionId() { return transactionId; }
public String getFromAccountNumber() { return fromAccountNumber; }
public String getToAccountNumber() { return toAccountNumber; }
public BigDecimal getAmount() { return amount; }
public Currency getCurrency() { return currency; }
public TransactionType getType() { return type; }
public String getDescription() { return description; }
public LocalDateTime getTimestamp() { return timestamp; }
public TransactionStatus getStatus() { return status; }
public void setStatus(TransactionStatus status) {
this.status = status;
}
@Override
public String toString() {
return String.format("%s: %s %s %.2f (%s) - %s",
timestamp, type, currency, amount, description, status);
}
}
// Exchange rate service
class ExchangeRateService {
private final Map<String, BigDecimal> rates;
public ExchangeRateService() {
rates = new ConcurrentHashMap<>();
initializeRates();
}
private void initializeRates() {
// Base currency: USD
rates.put("USD_USD", BigDecimal.ONE);
rates.put("USD_EUR", new BigDecimal("0.85"));
rates.put("USD_AZN", new BigDecimal("1.70"));
rates.put("USD_GBP", new BigDecimal("0.75"));
rates.put("USD_TRY", new BigDecimal("27.50"));
rates.put("EUR_USD", new BigDecimal("1.18"));
rates.put("EUR_EUR", BigDecimal.ONE);
rates.put("AZN_USD", new BigDecimal("0.59"));
rates.put("AZN_AZN", BigDecimal.ONE);
rates.put("GBP_USD", new BigDecimal("1.33"));
rates.put("GBP_GBP", BigDecimal.ONE);
rates.put("TRY_USD", new BigDecimal("0.036"));
rates.put("TRY_TRY", BigDecimal.ONE);
}
public BigDecimal convert(BigDecimal amount, Currency from, Currency to) {
if (from == to) {
return amount;
}
String rateKey = from.name() + "_" + to.name();
BigDecimal rate = rates.get(rateKey);
if (rate == null) {
throw new IllegalArgumentException("Exchange rate not available for " + from + " to " + to);
}
return amount.multiply(rate);
}
public void updateRate(Currency from, Currency to, BigDecimal rate) {
String rateKey = from.name() + "_" + to.name();
rates.put(rateKey, rate);
}
}
// Transfer strategy interface
interface TransferStrategy {
TransferResult executeTransfer(Account fromAccount, Account toAccount,
BigDecimal amount, String description, ExchangeRateService exchangeService);
}
// Internal transfer strategy
class InternalTransferStrategy implements TransferStrategy {
@Override
public TransferResult executeTransfer(Account fromAccount, Account toAccount,
BigDecimal amount, String description, ExchangeRateService exchangeService) {
String transactionId = UUID.randomUUID().toString();
// Lock accounts in consistent order to prevent deadlock
Account firstLock = fromAccount.getAccountNumber().compareTo(toAccount.getAccountNumber()) < 0
? fromAccount : toAccount;
Account secondLock = firstLock == fromAccount ? toAccount : fromAccount;
firstLock.lock.lock();
try {
secondLock.lock.lock();
try {
// Convert currency if needed
BigDecimal convertedAmount = amount;
if (fromAccount.getCurrency() != toAccount.getCurrency()) {
convertedAmount = exchangeService.convert(amount, fromAccount.getCurrency(),
toAccount.getCurrency());
}
// Attempt debit from source account
boolean debitSuccess = fromAccount.debit(amount, transactionId,
"Transfer to " + toAccount.getAccountNumber());
if (!debitSuccess) {
return new TransferResult(transactionId, false, "Kifayət qədər vəsait yoxdur");
}
try {
// Credit to destination account
toAccount.credit(convertedAmount, transactionId,
"Transfer from " + fromAccount.getAccountNumber());
return new TransferResult(transactionId, true, "Transfer uğurlu oldu");
} catch (Exception e) {
// Rollback debit if credit fails
fromAccount.reverseTransaction(transactionId);
return new TransferResult(transactionId, false, "Transfer uğursuz oldu: " + e.getMessage());
}
} finally {
secondLock.lock.unlock();
}
} finally {
firstLock.lock.unlock();
}
}
}
// External transfer strategy (simulation)
class ExternalTransferStrategy implements TransferStrategy {
@Override
public TransferResult executeTransfer(Account fromAccount, Account toAccount,
BigDecimal amount, String description, ExchangeRateService exchangeService) {
String transactionId = UUID.randomUUID().toString();
fromAccount.lock.lock();
try {
// For external transfers, we only debit from source account
// The credit to destination account would be handled by external system
boolean debitSuccess = fromAccount.debit(amount, transactionId,
"External transfer to " + toAccount.getAccountNumber());
if (!debitSuccess) {
return new TransferResult(transactionId, false, "Kifayət qədər vəsait yoxdur");
}
// Simulate external processing
try {
Thread.sleep(100); // Simulate network delay
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// In real implementation, this would involve calling external APIs
return new TransferResult(transactionId, true, "Xarici transfer başladı");
} finally {
fromAccount.lock.unlock();
}
}
}
// Transfer result class
class TransferResult {
private final String transactionId;
private final boolean success;
private final String message;
public TransferResult(String transactionId, boolean success, String message) {
this.transactionId = transactionId;
this.success = success;
this.message = message;
}
public String getTransactionId() { return transactionId; }
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
@Override
public String toString() {
return String.format("TransferResult{transactionId='%s', success=%s, message='%s'}",
transactionId, success, message);
}
}
// Notification service
interface NotificationService {
void sendNotification(String userId, String message);
}
class EmailNotificationService implements NotificationService {
@Override
public void sendNotification(String userId, String message) {
System.out.println("Email sent to user " + userId + ": " + message);
}
}
// Money transfer service
class MoneyTransferService {
private final Map<String, Account> accounts;
private final Map<String, User> users;
private final ExchangeRateService exchangeRateService;
private final NotificationService notificationService;
private final TransferStrategy internalTransferStrategy;
private final TransferStrategy externalTransferStrategy;
public MoneyTransferService() {
this.accounts = new ConcurrentHashMap<>();
this.users = new ConcurrentHashMap<>();
this.exchangeRateService = new ExchangeRateService();
this.notificationService = new EmailNotificationService();
this.internalTransferStrategy = new InternalTransferStrategy();
this.externalTransferStrategy = new ExternalTransferStrategy();
}
public void createUser(String userId, String name, String email) {
users.put(userId, new User(userId, name, email));
}
public String createAccount(String userId, AccountType accountType, Currency currency,
BigDecimal initialBalance) {
if (!users.containsKey(userId)) {
throw new IllegalArgumentException("İstifadəçi tapılmadı: " + userId);
}
String accountNumber = generateAccountNumber();
Account account = new Account(accountNumber, userId, accountType, currency, initialBalance);
accounts.put(accountNumber, account);
User user = users.get(userId);
user.addAccount(accountNumber);
return accountNumber;
}
public TransferResult transferMoney(String fromAccountNumber, String toAccountNumber,
BigDecimal amount, String description) {
Account fromAccount = accounts.get(fromAccountNumber);
Account toAccount = accounts.get(toAccountNumber);
if (fromAccount == null) {
return new TransferResult(null, false, "Mənbə hesab tapılmadı");
}
if (toAccount == null) {
return new TransferResult(null, false, "Təyinat hesabı tapılmadı");
}
if (!fromAccount.isActive() || !toAccount.isActive()) {
return new TransferResult(null, false, "Hesablardan biri aktiv deyil");
}
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
return new TransferResult(null, false, "Yanlış məbləğ");
}
// Choose strategy based on account ownership
TransferStrategy strategy = fromAccount.getUserId().equals(toAccount.getUserId())
? internalTransferStrategy : externalTransferStrategy;
TransferResult result = strategy.executeTransfer(fromAccount, toAccount, amount,
description, exchangeRateService);
// Send notifications
if (result.isSuccess()) {
notificationService.sendNotification(fromAccount.getUserId(),
"Transfer həyata keçirildi: " + amount + " " + fromAccount.getCurrency() +
" " + toAccount.getAccountNumber() + " hesabına");
if (!fromAccount.getUserId().equals(toAccount.getUserId())) {
notificationService.sendNotification(toAccount.getUserId(),
"Hesabınıza pul daxil oldu: " + amount + " " + toAccount.getCurrency());
}
} else {
notificationService.sendNotification(fromAccount.getUserId(),
"Transfer uğursuz oldu: " + result.getMessage());
}
return result;
}
public Account getAccount(String accountNumber) {
return accounts.get(accountNumber);
}
public List<Account> getUserAccounts(String userId) {
return accounts.values().stream()
.filter(account -> account.getUserId().equals(userId))
.toList();
}
public List<Transaction> getAccountTransactions(String accountNumber, int limit) {
Account account = accounts.get(accountNumber);
if (account != null) {
return account.getTransactionHistory(limit);
}
return new ArrayList<>();
}
private String generateAccountNumber() {
return "ACC" + System.currentTimeMillis();
}
}
// Example usage
public class MoneyTransferDemo {
public static void main(String[] args) {
MoneyTransferService transferService = new MoneyTransferService();
// Create users
transferService.createUser("user1", "John Doe", "john@example.com");
transferService.createUser("user2", "Jane Smith", "jane@example.com");
// Create accounts
String account1 = transferService.createAccount("user1", AccountType.CHECKING,
Currency.USD, new BigDecimal("1000.00"));
String account2 = transferService.createAccount("user1", AccountType.SAVINGS,
Currency.EUR, new BigDecimal("500.00"));
String account3 = transferService.createAccount("user2", AccountType.CHECKING,
Currency.AZN, new BigDecimal("2000.00"));
System.out.println("Yaradılmış hesablar:");
System.out.println("Account 1: " + account1 + " (USD)");
System.out.println("Account 2: " + account2 + " (EUR)");
System.out.println("Account 3: " + account3 + " (AZN)");
// Transfer money between same user accounts
System.out.println("\nEyni istifadəçinin hesabları arasında transfer:");
TransferResult result1 = transferService.transferMoney(account1, account2,
new BigDecimal("100.00"),
"Internal transfer");
System.out.println("Transfer result: " + result1);
// Transfer money between different users
System.out.println("\nMüxtəlif istifadəçilər arasında transfer:");
TransferResult result2 = transferService.transferMoney(account1, account3,
new BigDecimal("200.00"),
"Payment to friend");
System.out.println("Transfer result: " + result2);
// Check account balances
System.out.println("\nHesab balansları:");
System.out.println("Account 1 balance: " + transferService.getAccount(account1).getBalance() + " USD");
System.out.println("Account 2 balance: " + transferService.getAccount(account2).getBalance() + " EUR");
System.out.println("Account 3 balance: " + transferService.getAccount(account3).getBalance() + " AZN");
// View transaction history
System.out.println("\nTranzaksiya tarixçəsi (Account 1):");
List<Transaction> transactions = transferService.getAccountTransactions(account1, 10);
transactions.forEach(System.out::println);
}
}
Thread Safety Considerations
- ReentrantLock: Hər hesab üçün ayrı lock istifadə olunur
- Deadlock Prevention: Hesablar ardıcıl sıra ilə lock olunur
- Atomic Operations: Bütün kritik əməliyyatlar atomik şəkildə həyata keçirilir
- ConcurrentHashMap: Thread-safe kolleksiyalar istifadə olunur
ACID Properties
- Atomicity: Tranzaksiyalar ya tam həyata keçirilir ya da ləğv edilir
- Consistency: Sistem həmişə tutarlı vəziyyətdə qalır
- Isolation: Concurrent tranzaksiyalar bir-birinə təsir etmir
- Durability: Tamamlanmış tranzaksiyalar daimi saxlanır
Security Considerations
- Account Validation: Hesabların mövcudluğu və aktivliyi yoxlanılır
- Amount Validation: Transfer məbləğlərinin düzgünlüyü təmin edilir
- Currency Conversion: Valyuta çevirməsi etibarlı məzənnə ilə
- Audit Trail: Bütün əməliyyatlar log edilir
Additional Features
- Multi-Currency Support: Müxtəlif valyutalar və avtomatik çevirmə
- Notification System: Tranzaksiya statusu haqqında bildirişlər
- Transaction History: Ətraflı tranzaksiya tarixçəsi
- Rollback Mechanism: Uğursuz tranzaksiyaların geri alınması