Əsas məzmuna keçin

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

  1. 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
  2. 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

  1. Hesab İdarəetməsi: İstifadəçi hesablarının yaradılması və saxlanması
  2. Transfer Servisi: Pul transfer əməliyyatlarının icrası
  3. Tranzaksiya Meneceri: ACID xassələrini təmin edir
  4. Bildiriş Sistemi: İstifadəçilərə tranzaksiya statusu bildirir
  5. 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:

  1. Daxili Transfer: Eyni bank daxilində transfer
  2. Xarici Transfer: Müxtəlif banklar arasında transfer
  3. 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

  1. ReentrantLock: Hər hesab üçün ayrı lock istifadə olunur
  2. Deadlock Prevention: Hesablar ardıcıl sıra ilə lock olunur
  3. Atomic Operations: Bütün kritik əməliyyatlar atomik şəkildə həyata keçirilir
  4. ConcurrentHashMap: Thread-safe kolleksiyalar istifadə olunur

ACID Properties

  1. Atomicity: Tranzaksiyalar ya tam həyata keçirilir ya da ləğv edilir
  2. Consistency: Sistem həmişə tutarlı vəziyyətdə qalır
  3. Isolation: Concurrent tranzaksiyalar bir-birinə təsir etmir
  4. Durability: Tamamlanmış tranzaksiyalar daimi saxlanır

Security Considerations

  1. Account Validation: Hesabların mövcudluğu və aktivliyi yoxlanılır
  2. Amount Validation: Transfer məbləğlərinin düzgünlüyü təmin edilir
  3. Currency Conversion: Valyuta çevirməsi etibarlı məzənnə ilə
  4. Audit Trail: Bütün əməliyyatlar log edilir

Additional Features

  1. Multi-Currency Support: Müxtəlif valyutalar və avtomatik çevirmə
  2. Notification System: Tranzaksiya statusu haqqında bildirişlər
  3. Transaction History: Ətraflı tranzaksiya tarixçəsi
  4. Rollback Mechanism: Uğursuz tranzaksiyaların geri alınması