diff --git a/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountMapper.java b/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountMapper.java index 7454c7d..d8f4ba0 100644 --- a/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountMapper.java +++ b/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountMapper.java @@ -47,4 +47,14 @@ class AccountMapper { return new ActivityWindow(mappedActivities); } + ActivityJpaEntity mapToJpaEntity(Activity activity) { + return new ActivityJpaEntity( + activity.getId() == null ? null : activity.getId().getValue(), + activity.getTimestamp(), + activity.getOwnerAccountId().getValue(), + activity.getSourceAccountId().getValue(), + activity.getTargetAccountId().getValue(), + activity.getMoney().getAmount().longValue()); + } + } diff --git a/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountPersistenceAdapter.java b/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountPersistenceAdapter.java index cc8e7a2..03ec342 100644 --- a/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountPersistenceAdapter.java +++ b/adapters/cashpal-persistence/src/main/java/io/reflectoring/cashpal/adapter/persistence/AccountPersistenceAdapter.java @@ -62,18 +62,9 @@ class AccountPersistenceAdapter implements public void updateActivities(Account account) { for (Activity activity : account.getActivityWindow().getActivities()) { if (activity.getId() == null) { - activityRepository.save(mapToJpa(activity)); + activityRepository.save(accountMapper.mapToJpaEntity(activity)); } } } - private ActivityJpaEntity mapToJpa(Activity activity) { - return new ActivityJpaEntity( - activity.getId() == null ? null : activity.getId().getValue(), - activity.getTimestamp(), - activity.getOwnerAccountId().getValue(), - activity.getSourceAccountId().getValue(), - activity.getTargetAccountId().getValue(), - activity.getMoney().getAmount().longValue()); - } } diff --git a/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/AccountResource.java b/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/AccountResource.java new file mode 100644 index 0000000..741a24f --- /dev/null +++ b/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/AccountResource.java @@ -0,0 +1,4 @@ +package io.reflectoring.cashpal.adapter.web; + +class AccountResource { +} diff --git a/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/SendMoneyController.java b/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/SendMoneyController.java index adc2bb9..bebb826 100644 --- a/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/SendMoneyController.java +++ b/adapters/cashpal-web/src/main/java/io/reflectoring/cashpal/adapter/web/SendMoneyController.java @@ -4,12 +4,9 @@ import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase; import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand; import io.reflectoring.cashpal.domain.Account.AccountId; import io.reflectoring.cashpal.domain.Money; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @@ -18,24 +15,18 @@ public class SendMoneyController { private final SendMoneyUseCase sendMoneyUseCase; - @PostMapping(path = "/sendMoney") - void sendMoney(@RequestBody SendMoneyForm form) { + @PostMapping(path = "/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}") + void sendMoney( + @PathVariable("sourceAccountId") Long sourceAccountId, + @PathVariable("targetAccountId") Long targetAccountId, + @PathVariable("amount") Long amount) { SendMoneyCommand command = new SendMoneyCommand( - new AccountId(form.sourceAccountId), - new AccountId(form.targetAccountId), - Money.of(form.amount)); + new AccountId(sourceAccountId), + new AccountId(targetAccountId), + Money.of(amount)); sendMoneyUseCase.sendMoney(command); } - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class SendMoneyForm { - private Long sourceAccountId; - private Long targetAccountId; - private Long amount; - } - } diff --git a/adapters/cashpal-web/src/test/java/io/reflectoring/cashpal/adapter/web/SendMoneyControllerTest.java b/adapters/cashpal-web/src/test/java/io/reflectoring/cashpal/adapter/web/SendMoneyControllerTest.java index 5575923..6b38a75 100644 --- a/adapters/cashpal-web/src/test/java/io/reflectoring/cashpal/adapter/web/SendMoneyControllerTest.java +++ b/adapters/cashpal-web/src/test/java/io/reflectoring/cashpal/adapter/web/SendMoneyControllerTest.java @@ -1,7 +1,5 @@ package io.reflectoring.cashpal.adapter.web; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.reflectoring.cashpal.adapter.web.SendMoneyController.SendMoneyForm; import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase; import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand; import org.junit.jupiter.api.Test; @@ -22,20 +20,11 @@ class SendMoneyControllerTest { @MockBean private SendMoneyUseCase sendMoneyUseCase; - private ObjectMapper jsonMapper = new ObjectMapper(); - @Test void testSendMoney() throws Exception { - SendMoneyForm form = new SendMoneyForm( - 41L, - 42L, - 500L - ); - - mockMvc.perform(post("/sendMoney") - .header("Content-Type", "application/json") - .content(jsonMapper.writeValueAsString(form))) + mockMvc.perform(post("/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}", 41L, 42L, 500) + .header("Content-Type", "application/json")) .andExpect(status().isOk()); then(sendMoneyUseCase).should().sendMoney(any(SendMoneyCommand.class)); diff --git a/cashpal-application/src/main/java/io/reflectoring/cashpal/application/port/in/SendMoneyUseCase.java b/cashpal-application/src/main/java/io/reflectoring/cashpal/application/port/in/SendMoneyUseCase.java index e9be53a..d651666 100644 --- a/cashpal-application/src/main/java/io/reflectoring/cashpal/application/port/in/SendMoneyUseCase.java +++ b/cashpal-application/src/main/java/io/reflectoring/cashpal/application/port/in/SendMoneyUseCase.java @@ -17,15 +17,18 @@ public interface SendMoneyUseCase { class SendMoneyCommand extends SelfValidating { @NotNull - private Account.AccountId sourceAccountId; + private final Account.AccountId sourceAccountId; @NotNull - private Account.AccountId targetAccountId; + private final Account.AccountId targetAccountId; @NotNull - private Money money; + private final Money money; - public SendMoneyCommand(Account.AccountId sourceAccountId, Account.AccountId targetAccountId, Money money) { + public SendMoneyCommand( + Account.AccountId sourceAccountId, + Account.AccountId targetAccountId, + Money money) { this.sourceAccountId = sourceAccountId; this.targetAccountId = targetAccountId; this.money = money; diff --git a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Account.java b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Account.java index 84b2c53..fa28443 100644 --- a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Account.java +++ b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Account.java @@ -53,7 +53,9 @@ public class Account { * Calculates the total balance of the account by adding the activity values to the baseline balance. */ public Money calculateBalance() { - return Money.add(this.activityWindow.calculateBalance(this.id), this.baselineBalance); + return Money.add( + this.baselineBalance, + this.activityWindow.calculateBalance(this.id)); } /** @@ -63,22 +65,39 @@ public class Account { */ public boolean withdraw(Money money, AccountId targetAccountId) { - if(Money.add(this.calculateBalance(), money.negate()).isNegative()){ + if (!mayWithdraw(money)) { return false; } - Activity withdrawal = new Activity(null, this.id, this.id, targetAccountId, LocalDateTime.now(), money); + Activity withdrawal = new Activity( + this.id, + this.id, + targetAccountId, + LocalDateTime.now(), + money); this.activityWindow.addActivity(withdrawal); return true; } + private boolean mayWithdraw(Money money) { + return Money.add( + this.calculateBalance(), + money.negate()) + .isPositiveOrZero(); + } + /** * Tries to deposit a certain amount of money to this account. * If sucessful, creates a new activity with a positive value. * @return true if the deposit was successful, false if not. */ public boolean deposit(Money money, AccountId sourceAccountId) { - Activity deposit = new Activity(null, this.id, sourceAccountId, this.id, LocalDateTime.now(), money); + Activity deposit = new Activity( + this.id, + sourceAccountId, + this.id, + LocalDateTime.now(), + money); this.activityWindow.addActivity(deposit); return true; } diff --git a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Activity.java b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Activity.java index 42cdb1c..c333ec9 100644 --- a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Activity.java +++ b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Activity.java @@ -4,16 +4,18 @@ import java.time.LocalDateTime; import lombok.Getter; import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.Value; /** * A money transfer activity between {@link Account}s. */ @Value +@RequiredArgsConstructor public class Activity { @Getter - private final ActivityId id; + private ActivityId id; /** * The account that owns this activity. @@ -50,6 +52,20 @@ public class Activity { @NonNull private final Money money; + public Activity( + @NonNull Account.AccountId ownerAccountId, + @NonNull Account.AccountId sourceAccountId, + @NonNull Account.AccountId targetAccountId, + @NonNull LocalDateTime timestamp, + @NonNull Money money) { + this.id = null; + this.ownerAccountId = ownerAccountId; + this.sourceAccountId = sourceAccountId; + this.targetAccountId = targetAccountId; + this.timestamp = timestamp; + this.money = money; + } + @Value public static class ActivityId { private final Long value; diff --git a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Money.java b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Money.java index 683fe27..cee9708 100644 --- a/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Money.java +++ b/cashpal-application/src/main/java/io/reflectoring/cashpal/domain/Money.java @@ -37,6 +37,14 @@ public class Money { return new Money(a.amount.add(b.amount)); } + public Money minus(Money money){ + return new Money(this.amount.subtract(money.amount)); + } + + public Money plus(Money money){ + return new Money(this.amount.add(money.amount)); + } + public static Money subtract(Money a, Money b) { return new Money(a.amount.subtract(b.amount)); } diff --git a/cashpal-application/src/test/java/io/reflectoring/cashpal/application/service/SendMoneyServiceTest.java b/cashpal-application/src/test/java/io/reflectoring/cashpal/application/service/SendMoneyServiceTest.java index a70be93..b973974 100644 --- a/cashpal-application/src/test/java/io/reflectoring/cashpal/application/service/SendMoneyServiceTest.java +++ b/cashpal-application/src/test/java/io/reflectoring/cashpal/application/service/SendMoneyServiceTest.java @@ -60,11 +60,8 @@ class SendMoneyServiceTest { @Test void transactionSucceeds() { - AccountId sourceAccountId = new AccountId(41L); - Account sourceAccount = givenAnAccountWithId(sourceAccountId); - - AccountId targetAccountId = new AccountId(42L); - Account targetAccount = givenAnAccountWithId(targetAccountId); + Account sourceAccount = givenSourceAccount(); + Account targetAccount = givenTargetAccount(); givenWithdrawalWillSucceed(sourceAccount); givenDepositWillSucceed(targetAccount); @@ -72,14 +69,17 @@ class SendMoneyServiceTest { Money money = Money.of(500L); SendMoneyCommand command = new SendMoneyCommand( - sourceAccountId, - targetAccountId, + sourceAccount.getId(), + targetAccount.getId(), money); boolean success = sendMoneyService.sendMoney(command); assertThat(success).isTrue(); + AccountId sourceAccountId = sourceAccount.getId(); + AccountId targetAccountId = targetAccount.getId(); + then(accountLock).should().lockAccount(eq(sourceAccountId)); then(sourceAccount).should().withdraw(eq(money), eq(targetAccountId)); then(accountLock).should().releaseAccount(eq(sourceAccountId)); @@ -121,6 +121,14 @@ class SendMoneyServiceTest { .willReturn(true); } + private Account givenTargetAccount(){ + return givenAnAccountWithId(new AccountId(42L)); + } + + private Account givenSourceAccount(){ + return givenAnAccountWithId(new AccountId(41L)); + } + private Account givenAnAccountWithId(AccountId id) { Account account = Mockito.mock(Account.class); given(account.getId())