Sobes.tech
Senior

Implement a transfer of funds between two accounts. Note that this is a production solution Input parameters: @param fromAccountId the unique identifier of the user transferring funds @param toAccountId the unique identifier of the user receiving funds @param amount the transfer amount. A positive number public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) { if (amount.compareTo(BigDecimal.ZERO) <= 0) { throw new RuntimeException("Amount must be > 0"); } if (fromAccountId.equals(toAccountId)) { throw new RuntimeException("Account can't be the same"); } Long firstId = Math.min(fromAccountId, toAccountId); Long secondId = Math.max(fromAccountId, toAccountId); Account first = accountRepository.findByIdForUpdate(firstId) .orElseThrow(() -> new RuntimeException("Account not found")); Account second = accountRepository.findByIdForUpdate(secondId) .orElseThrow(() -> new RuntimeException("Account not found")); Account from = fromAccountId.equals(firstId) ? first : second; Account to = fromAccountId.equals(firstId) ? second : first; if (from.getBalance().compareTo(amount) < 0) { throw new RuntimeException("Balance is Less 0"); } from.setBalance(from.getBalance().subtract(amount)); to.setBalance(from.getBalance().add(amount)); accountRepository.save(to); accountRepository.save(from); }

sobes.tech AI

Answer from AI

The provided code has several important points, but also a critical error that will lead to incorrect balance updates.

  1. Input parameter and logic checks:
  • Checks that the transfer amount is positive.
  • Checks that the accounts are different.
  • To prevent deadlocks, accounts are locked in ascending order of ID.
  1. Error in updating balances:
from.setBalance(from.getBalance().subtract(amount));
 to.setBalance(from.getBalance().add(amount));

Here, after subtracting the amount from from, the balance from.getBalance() is already decreased, and when adding to to, the already decreased from balance is used, which is incorrect. The old balance of to should be used.

Correct version:

from.setBalance(from.getBalance().subtract(amount));
 to.setBalance(to.getBalance().add(amount));
  1. Transactionality:

For production, it is essential to wrap the operation in a transaction to ensure atomicity and data consistency.

  1. Exceptions:

It is better to use specialized exceptions rather than RuntimeException.

  1. Example of corrected code with transaction (e.g., with Spring):
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
 if (amount.compareTo(BigDecimal.ZERO) <= 0) {
 throw new IllegalArgumentException("Amount must be > 0");
 }
 if (fromAccountId.equals(toAccountId)) {
 throw new IllegalArgumentException("Account can't be the same");
 }

 Long firstId = Math.min(fromAccountId, toAccountId);
 Long secondId = Math.max(fromAccountId, toAccountId);

 Account first = accountRepository.findByIdForUpdate(firstId)
 .orElseThrow(() -> new EntityNotFoundException("Account not found"));
 Account second = accountRepository.findByIdForUpdate(secondId)
 .orElseThrow(() -> new EntityNotFoundException("Account not found"));

 Account from = fromAccountId.equals(firstId) ? first : second;
 Account to = fromAccountId.equals(firstId) ? second : first;

 if (from.getBalance().compareTo(amount) < 0) {
 throw new IllegalStateException("Insufficient funds");
 }

 from.setBalance(from.getBalance().subtract(amount));
 to.setBalance(to.getBalance().add(amount));

 accountRepository.save(from);
 accountRepository.save(to);
}

Thus, the key points are correct balance updates, record locking to prevent race conditions, transactionality, and proper error handling.

Implement a transfer of funds between two accounts… - sobes.tech