diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4ddff09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+### IntelliJ IDEA ###
+.idea
+target
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2ca15ec..0e222a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,10 +3,44 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
-
+
+ UTF-8
+ 1.8
+ 1.8
+
ch.engenius
accounts
1.0-SNAPSHOT
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.24
+ provided
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.9.0
+ test
+
+
\ No newline at end of file
diff --git a/src/main/java/ch/engenius/bank/Account.java b/src/main/java/ch/engenius/bank/Account.java
deleted file mode 100644
index b9979cb..0000000
--- a/src/main/java/ch/engenius/bank/Account.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package ch.engenius.bank;
-
-import java.math.BigDecimal;
-
-public class Account {
- private double money;
-
- public void withdraw(double amount) {
- if ((money - amount) < 0) {
- throw new IllegalStateException("not enough credits on account");
- }
- setMoney(money - amount);
-
- }
-
- public void deposit(double amount) {
- setMoney(money + amount);
- }
-
- public double getMoney() {
- return money;
- }
-
- public void setMoney(double money) {
- this.money = money;
- }
-
- public BigDecimal getMoneyAsBigDecimal() {
- return BigDecimal.valueOf(money);
- }
-}
diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java
deleted file mode 100644
index 571ebc7..0000000
--- a/src/main/java/ch/engenius/bank/Bank.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package ch.engenius.bank;
-
-import java.util.HashMap;
-
-public class Bank {
- private HashMap accounts = new HashMap<>();
-
- public Account registerAccount(int accountNumber, int amount) {
- Account account = new Account();
- account.setMoney(amount);
- accounts.put(accountNumber, account);
- return account;
- }
-
- public Account getAccount( int number) {
- return accounts.get(number);
- }
-}
diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java
index 10b30fd..500119c 100644
--- a/src/main/java/ch/engenius/bank/BankRunner.java
+++ b/src/main/java/ch/engenius/bank/BankRunner.java
@@ -1,7 +1,13 @@
package ch.engenius.bank;
+
+import ch.engenius.bank.domain.Bank;
+import ch.engenius.bank.domain.BankAccount;
+import ch.engenius.bank.exceptions.BankMoneyAmountException;
+
import java.math.BigDecimal;
-import java.util.Random;
+import java.util.HashMap;
+import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -9,63 +15,68 @@
public class BankRunner {
- private static final ExecutorService executor = Executors.newFixedThreadPool(8);
-
- private final Random random = new Random(43);
- private final Bank bank = new Bank();
+ private static final int NUMBER_OF_BANK_ACCOUNTS = 1000;
+ private static final int NUMBER_OF_ITERATIONS = 1000;
+ private static final BigDecimal DEFAULT_DEPOSIT = BigDecimal.valueOf(100);
+ public static final BigDecimal TOTAL_BANK_MONEY_AMOUNT = DEFAULT_DEPOSIT.multiply(BigDecimal.valueOf(NUMBER_OF_BANK_ACCOUNTS));
+ private static final ExecutorService executorService = Executors.newFixedThreadPool(8);
+ private final Bank bank;
+ public BankRunner() {
+ HashMap accounts = new HashMap<>();
+ this.bank = new Bank(accounts);
+ }
public static void main(String[] args) {
BankRunner runner = new BankRunner();
- int accounts = 100;
- int defaultDeposit = 1000;
- int iterations = 10000;
- runner.registerAccounts(accounts, defaultDeposit);
- runner.sanityCheck(accounts, accounts*defaultDeposit);
- runner.runBank(iterations, accounts);
- runner.sanityCheck(accounts, accounts*defaultDeposit);
+ runner.registerAccounts();
+ runner.bankMoneyAmountCheck();
+ runner.runTransactions();
+ runner.bankMoneyAmountCheck();
+ }
+ private void registerAccounts() {
+ IntStream.range(0, NUMBER_OF_BANK_ACCOUNTS).forEach(k ->
+ bank.registerBankAccount(UUID.randomUUID(), BankRunner.DEFAULT_DEPOSIT)
+ );
}
- private void runBank(int iterations, int maxAccount) {
- for (int i =0; i< iterations; i++ ) {
- executor.submit( ()-> runRandomOperation(maxAccount));
+ private void bankMoneyAmountCheck() {
+ BigDecimal currentTotalAmount = bank.getAccounts().values()
+ .stream()
+ .map(BankAccount::getMoneyAmount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ if (TOTAL_BANK_MONEY_AMOUNT.compareTo(currentTotalAmount) != 0) {
+ throw new BankMoneyAmountException("Bank money amount check failed.");
}
+ System.out.println("Bank money amount check successful.");
+ }
+
+ private void runTransactions() {
+ for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) {
+ executorService.submit(this::transferMoneyFromAndToRandomBankAccount);
+ }
+ executorService.shutdown();
try {
- executor.shutdown();
- executor.awaitTermination(100,TimeUnit.SECONDS);
+ if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
} catch (InterruptedException e) {
- e.printStackTrace();
+ System.out.println("InterruptedException occurred: " + e);
+ executorService.shutdownNow();
}
}
- private void runRandomOperation(int maxAccount) {
- double transfer = random.nextDouble()*100.0;
- int accountInNumber = random.nextInt(maxAccount);
- int accountOutNumber = random.nextInt(maxAccount);
- Account accIn =bank.getAccount(accountInNumber);
- Account accOut =bank.getAccount(accountOutNumber);
- accIn.deposit(transfer);
- accOut.withdraw(transfer);
- }
+ private void transferMoneyFromAndToRandomBankAccount() {
+ BigDecimal transferMoneyAmount = BigDecimal.valueOf(Math.random() * DEFAULT_DEPOSIT.doubleValue());
- private void registerAccounts(int number, int defaultMoney) {
- for ( int i = 0; i < number; i++) {
- bank.registerAccount(i, defaultMoney);
+ UUID accountInId = bank.getRandomBankAccountId();
+ UUID accountOutId = bank.getRandomBankAccountId();
+ while (accountInId.equals(accountOutId)) {
+ accountOutId = bank.getRandomBankAccountId();
}
- }
-
- private void sanityCheck( int accountMaxNumber, int totalExpectedMoney) {
- BigDecimal sum = IntStream.range(0, accountMaxNumber)
- .mapToObj( bank::getAccount)
- .map ( Account::getMoneyAsBigDecimal)
- .reduce( BigDecimal.ZERO, BigDecimal::add);
- if ( sum.intValue() != totalExpectedMoney) {
- throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)");
- }
- System.out.println("sanity check OK");
+ bank.transferMoney(accountOutId, accountInId, transferMoneyAmount);
}
-
-
}
diff --git a/src/main/java/ch/engenius/bank/domain/Bank.java b/src/main/java/ch/engenius/bank/domain/Bank.java
new file mode 100644
index 0000000..78c0614
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/domain/Bank.java
@@ -0,0 +1,44 @@
+package ch.engenius.bank.domain;
+
+import ch.engenius.bank.exceptions.NotFoundException;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class Bank {
+
+ private HashMap accounts = new HashMap<>();
+
+ public void registerBankAccount(UUID accountNumber, BigDecimal initialMoneyAmount) {
+ BankAccount account = new BankAccount();
+ account.setMoneyAmount(initialMoneyAmount);
+ account.setAccountNumber(accountNumber);
+ this.accounts.put(accountNumber, account);
+ }
+
+ @Synchronized
+ public void transferMoney(UUID bankAccountOutId, UUID bankAccountInId, BigDecimal moneyAmount) {
+ BankAccount bankAccountOut = getBankAccount(bankAccountOutId);
+ BankAccount bankAccountIn = getBankAccount(bankAccountInId);
+
+ bankAccountOut.withdraw(moneyAmount);
+ bankAccountIn.deposit(moneyAmount);
+ }
+
+ public BankAccount getBankAccount(UUID accountNumber) {
+ if (this.accounts.get(accountNumber) == null) {
+ throw new NotFoundException("Bank account not found.");
+ }
+ return this.accounts.get(accountNumber);
+ }
+
+ public UUID getRandomBankAccountId() {
+ List accountIds = new ArrayList<>(getAccounts().keySet());
+ return accountIds.get(new Random().nextInt(accountIds.size()));
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/domain/BankAccount.java b/src/main/java/ch/engenius/bank/domain/BankAccount.java
new file mode 100644
index 0000000..bacb393
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/domain/BankAccount.java
@@ -0,0 +1,30 @@
+package ch.engenius.bank.domain;
+
+import ch.engenius.bank.exceptions.NotEnoughCreditsException;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class BankAccount {
+
+ private UUID accountNumber;
+ private BigDecimal moneyAmount;
+
+ @Synchronized
+ public void withdraw(BigDecimal amount) {
+ if ((moneyAmount.compareTo(amount)) < 0) {
+ throw new NotEnoughCreditsException("Not enough credits on bank account.");
+ }
+ setMoneyAmount(moneyAmount.subtract(amount));
+ }
+
+ @Synchronized
+ public void deposit(BigDecimal amount) {
+ setMoneyAmount(moneyAmount.add(amount));
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/exceptions/BankMoneyAmountException.java b/src/main/java/ch/engenius/bank/exceptions/BankMoneyAmountException.java
new file mode 100644
index 0000000..04d304f
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/exceptions/BankMoneyAmountException.java
@@ -0,0 +1,8 @@
+package ch.engenius.bank.exceptions;
+
+public class BankMoneyAmountException extends RuntimeException {
+
+ public BankMoneyAmountException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/exceptions/NotEnoughCreditsException.java b/src/main/java/ch/engenius/bank/exceptions/NotEnoughCreditsException.java
new file mode 100644
index 0000000..a1d03a5
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/exceptions/NotEnoughCreditsException.java
@@ -0,0 +1,8 @@
+package ch.engenius.bank.exceptions;
+
+public class NotEnoughCreditsException extends RuntimeException {
+
+ public NotEnoughCreditsException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/exceptions/NotFoundException.java b/src/main/java/ch/engenius/bank/exceptions/NotFoundException.java
new file mode 100644
index 0000000..0610a84
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/exceptions/NotFoundException.java
@@ -0,0 +1,8 @@
+package ch.engenius.bank.exceptions;
+
+public class NotFoundException extends RuntimeException {
+
+ public NotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/test/ch/engenius/bank/domain/BankAccountTest.java b/src/main/test/ch/engenius/bank/domain/BankAccountTest.java
new file mode 100644
index 0000000..b501574
--- /dev/null
+++ b/src/main/test/ch/engenius/bank/domain/BankAccountTest.java
@@ -0,0 +1,47 @@
+package ch.engenius.bank.domain;
+
+import ch.engenius.bank.exceptions.NotEnoughCreditsException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BankAccountTest {
+
+ private static final BigDecimal DEFAULT_MONEY_AMOUNT_TEN = BigDecimal.TEN;
+ private static final BigDecimal WITHDRAW_MONEY_AMOUNT_FIVE = BigDecimal.valueOf(5);
+ private static final BigDecimal MONEY_DEPOSIT = BigDecimal.TEN;
+ private final BankAccount bankAccount = new BankAccount();
+
+ @BeforeEach
+ public void setUp() {
+ bankAccount.setAccountNumber(UUID.randomUUID());
+ bankAccount.setMoneyAmount(DEFAULT_MONEY_AMOUNT_TEN);
+ }
+
+ @Test
+ public void shouldThrowNotEnoughCreditsExceptionWhenTryingToWithdraw() {
+ try {
+ bankAccount.withdraw(DEFAULT_MONEY_AMOUNT_TEN.multiply(BigDecimal.TEN));
+ } catch (NotEnoughCreditsException e) {
+ assertEquals("Not enough credits on bank account.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void shouldWithdrawSuccessfully() {
+ bankAccount.withdraw(WITHDRAW_MONEY_AMOUNT_FIVE);
+
+ assertEquals(DEFAULT_MONEY_AMOUNT_TEN.subtract(WITHDRAW_MONEY_AMOUNT_FIVE), bankAccount.getMoneyAmount());
+ }
+
+ @Test
+ public void shouldDepositMoneySuccessfully() {
+ bankAccount.deposit(MONEY_DEPOSIT);
+
+ assertEquals(DEFAULT_MONEY_AMOUNT_TEN.add(MONEY_DEPOSIT), bankAccount.getMoneyAmount());
+ }
+}
diff --git a/src/main/test/ch/engenius/bank/domain/BankTest.java b/src/main/test/ch/engenius/bank/domain/BankTest.java
new file mode 100644
index 0000000..c43600c
--- /dev/null
+++ b/src/main/test/ch/engenius/bank/domain/BankTest.java
@@ -0,0 +1,93 @@
+package ch.engenius.bank.domain;
+
+import ch.engenius.bank.exceptions.NotEnoughCreditsException;
+import ch.engenius.bank.exceptions.NotFoundException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class BankTest {
+ private static final UUID BANK_ACCOUNT_ONE_ID = UUID.nameUUIDFromBytes("first account".getBytes());
+ private static final UUID BANK_ACCOUNT_TWO_ID = UUID.nameUUIDFromBytes("second account".getBytes());
+ private static final BigDecimal DEFAULT_MONEY_AMOUNT_TEN = BigDecimal.TEN;
+ private static final BigDecimal TOO_BIG_AMOUNT_FOR_WITHDRAW = DEFAULT_MONEY_AMOUNT_TEN.multiply(DEFAULT_MONEY_AMOUNT_TEN);
+
+ private final Bank bank = new Bank();
+
+ @BeforeEach
+ public void setUp() {
+ HashMap accounts = new HashMap<>();
+ BankAccount bankAccountOne = new BankAccount(BANK_ACCOUNT_ONE_ID, DEFAULT_MONEY_AMOUNT_TEN);
+ BankAccount bankAccountTwo = new BankAccount(BANK_ACCOUNT_TWO_ID, DEFAULT_MONEY_AMOUNT_TEN);
+ accounts.put(BANK_ACCOUNT_ONE_ID, bankAccountOne);
+ accounts.put(BANK_ACCOUNT_TWO_ID, bankAccountTwo);
+ bank.setAccounts(accounts);
+ }
+
+ @Test
+ public void shouldRegisterNewBankAccountSuccessfully() {
+ UUID bankAccountIdExpected = UUID.randomUUID();
+ BigDecimal bankAccountAmountExpected = BigDecimal.TEN;
+ bank.registerBankAccount(bankAccountIdExpected, bankAccountAmountExpected);
+
+ BankAccount actualBankAccount = bank.getAccounts().get(bankAccountIdExpected);
+ assertNotNull(actualBankAccount);
+ assertEquals(bankAccountIdExpected, actualBankAccount.getAccountNumber());
+ assertEquals(bankAccountAmountExpected, actualBankAccount.getMoneyAmount());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenBankAccountWithProvidedIdDoesNotExistWhenTransferingMoney() {
+ try {
+ bank.transferMoney(BANK_ACCOUNT_ONE_ID, UUID.randomUUID(), BigDecimal.ONE);
+ } catch (NotFoundException e) {
+ assertEquals("Bank account not found.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void shouldThrowNotEnoughCreditsExceptionWhenBankAccountWithProvidedIdDoesNotExistWhenTransferingMoney() {
+ try {
+ bank.transferMoney(BANK_ACCOUNT_ONE_ID, BANK_ACCOUNT_TWO_ID, TOO_BIG_AMOUNT_FOR_WITHDRAW);
+ } catch (NotEnoughCreditsException e) {
+ assertEquals("Not enough credits on bank account.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void shouldTransferMoneySuccessfully() {
+ BigDecimal moneyAmount = BigDecimal.ONE;
+ bank.transferMoney(BANK_ACCOUNT_ONE_ID, BANK_ACCOUNT_TWO_ID, BigDecimal.ONE);
+
+ BankAccount bankAccountOne = bank.getAccounts().get(BANK_ACCOUNT_ONE_ID);
+ BankAccount bankAccountTwo = bank.getAccounts().get(BANK_ACCOUNT_TWO_ID);
+
+ assertEquals(0, bankAccountOne.getMoneyAmount().compareTo(DEFAULT_MONEY_AMOUNT_TEN.subtract(moneyAmount)));
+ assertEquals(0, bankAccountTwo.getMoneyAmount().compareTo(DEFAULT_MONEY_AMOUNT_TEN.add(moneyAmount)));
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenBankAccountWithProvidedIdDoesNotExist() {
+ try {
+ bank.getBankAccount(UUID.randomUUID());
+ } catch (NotFoundException e) {
+ assertEquals("Bank account not found.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void shouldReturnAccountSuccessfully() {
+ BankAccount bankAccountExpected = bank.getAccounts().get(BANK_ACCOUNT_ONE_ID);
+
+ BankAccount actualBankAccount = bank.getBankAccount(BANK_ACCOUNT_ONE_ID);
+ assertNotNull(actualBankAccount);
+ assertEquals(bankAccountExpected.getAccountNumber(), actualBankAccount.getAccountNumber());
+ assertEquals(bankAccountExpected.getMoneyAmount(), actualBankAccount.getMoneyAmount());
+ }
+}