simplifiex code examples
This commit is contained in:
@@ -12,9 +12,10 @@ import io.reflectoring.buckpal.domain.Account;
|
||||
import io.reflectoring.buckpal.domain.Account.AccountId;
|
||||
import io.reflectoring.buckpal.domain.Activity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@PersistenceAdapter
|
||||
@Component
|
||||
class AccountPersistenceAdapter implements
|
||||
LoadAccountPort,
|
||||
UpdateAccountStatePort {
|
||||
@@ -24,7 +25,9 @@ class AccountPersistenceAdapter implements
|
||||
private final AccountMapper accountMapper;
|
||||
|
||||
@Override
|
||||
public Account loadAccount(AccountId accountId, LocalDateTime baselineDate) {
|
||||
public Account loadAccount(
|
||||
AccountId accountId,
|
||||
LocalDateTime baselineDate) {
|
||||
|
||||
AccountJpaEntity account =
|
||||
accountRepository.findById(accountId.getValue())
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SendMoneyController {
|
||||
|
||||
private final SendMoneyUseCase sendMoneyUseCase;
|
||||
|
||||
@PostMapping(path = "/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}")
|
||||
@PostMapping(path = "/accounts/send/{sourceAccountId}/{targetAccountId}/{amount}")
|
||||
void sendMoney(
|
||||
@PathVariable("sourceAccountId") Long sourceAccountId,
|
||||
@PathVariable("targetAccountId") Long targetAccountId,
|
||||
|
||||
@@ -25,7 +25,7 @@ class SendMoneyControllerTest {
|
||||
@Test
|
||||
void testSendMoney() throws Exception {
|
||||
|
||||
mockMvc.perform(post("/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}",
|
||||
mockMvc.perform(post("/accounts/send/{sourceAccountId}/{targetAccountId}/{amount}",
|
||||
41L, 42L, 500)
|
||||
.header("Content-Type", "application/json"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.reflectoring.buckpal.application.port.in;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.reflectoring.buckpal.domain.Account;
|
||||
import io.reflectoring.buckpal.domain.Account.AccountId;
|
||||
import io.reflectoring.buckpal.domain.Money;
|
||||
import io.reflectoring.buckpal.testdata.SelfValidating;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public interface SendMoneyUseCase {
|
||||
|
||||
boolean sendMoney(SendMoneyCommand command);
|
||||
@@ -17,17 +17,17 @@ public interface SendMoneyUseCase {
|
||||
class SendMoneyCommand extends SelfValidating<SendMoneyCommand> {
|
||||
|
||||
@NotNull
|
||||
private final Account.AccountId sourceAccountId;
|
||||
private final AccountId sourceAccountId;
|
||||
|
||||
@NotNull
|
||||
private final Account.AccountId targetAccountId;
|
||||
private final AccountId targetAccountId;
|
||||
|
||||
@NotNull
|
||||
private final Money money;
|
||||
|
||||
public SendMoneyCommand(
|
||||
Account.AccountId sourceAccountId,
|
||||
Account.AccountId targetAccountId,
|
||||
AccountId sourceAccountId,
|
||||
AccountId targetAccountId,
|
||||
Money money) {
|
||||
this.sourceAccountId = sourceAccountId;
|
||||
this.targetAccountId = targetAccountId;
|
||||
|
||||
@@ -5,14 +5,16 @@ import io.reflectoring.buckpal.application.port.out.AccountLock;
|
||||
import io.reflectoring.buckpal.application.port.out.LoadAccountPort;
|
||||
import io.reflectoring.buckpal.application.port.out.UpdateAccountStatePort;
|
||||
import io.reflectoring.buckpal.domain.Account;
|
||||
import io.reflectoring.buckpal.domain.Account.AccountId;
|
||||
import io.reflectoring.buckpal.testdata.UseCase;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@UseCase
|
||||
@Component
|
||||
@Transactional
|
||||
public class SendMoneyService implements SendMoneyUseCase {
|
||||
|
||||
@@ -24,9 +26,7 @@ public class SendMoneyService implements SendMoneyUseCase {
|
||||
@Override
|
||||
public boolean sendMoney(SendMoneyCommand command) {
|
||||
|
||||
if(command.getMoney().isGreaterThan(moneyTransferProperties.getMaximumTransferThreshold())){
|
||||
throw new ThresholdExceededException(moneyTransferProperties.getMaximumTransferThreshold(), command.getMoney());
|
||||
}
|
||||
checkThreshold(command);
|
||||
|
||||
LocalDateTime baselineDate = LocalDateTime.now().minusDays(10);
|
||||
|
||||
@@ -38,25 +38,36 @@ public class SendMoneyService implements SendMoneyUseCase {
|
||||
command.getTargetAccountId(),
|
||||
baselineDate);
|
||||
|
||||
accountLock.lockAccount(sourceAccount.getId());
|
||||
if (!sourceAccount.withdraw(command.getMoney(), targetAccount.getId())) {
|
||||
accountLock.releaseAccount(sourceAccount.getId());
|
||||
AccountId sourceAccountId = sourceAccount.getId()
|
||||
.orElseThrow(() -> new IllegalStateException("expected source account ID not to be empty"));
|
||||
AccountId targetAccountId = targetAccount.getId()
|
||||
.orElseThrow(() -> new IllegalStateException("expected target account ID not to be empty"));
|
||||
|
||||
accountLock.lockAccount(sourceAccountId);
|
||||
if (!sourceAccount.withdraw(command.getMoney(), targetAccountId)) {
|
||||
accountLock.releaseAccount(sourceAccountId);
|
||||
return false;
|
||||
}
|
||||
|
||||
accountLock.lockAccount(targetAccount.getId());
|
||||
if (!targetAccount.deposit(command.getMoney(), sourceAccount.getId())) {
|
||||
accountLock.releaseAccount(sourceAccount.getId());
|
||||
accountLock.releaseAccount(targetAccount.getId());
|
||||
accountLock.lockAccount(targetAccountId);
|
||||
if (!targetAccount.deposit(command.getMoney(), sourceAccountId)) {
|
||||
accountLock.releaseAccount(sourceAccountId);
|
||||
accountLock.releaseAccount(targetAccountId);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateAccountStatePort.updateActivities(sourceAccount);
|
||||
updateAccountStatePort.updateActivities(targetAccount);
|
||||
|
||||
accountLock.releaseAccount(sourceAccount.getId());
|
||||
accountLock.releaseAccount(targetAccount.getId());
|
||||
accountLock.releaseAccount(sourceAccountId);
|
||||
accountLock.releaseAccount(targetAccountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkThreshold(SendMoneyCommand command) {
|
||||
if(command.getMoney().isGreaterThan(moneyTransferProperties.getMaximumTransferThreshold())){
|
||||
throw new ThresholdExceededException(moneyTransferProperties.getMaximumTransferThreshold(), command.getMoney());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.reflectoring.buckpal.domain;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -19,36 +20,43 @@ public class Account {
|
||||
/**
|
||||
* The unique ID of the account.
|
||||
*/
|
||||
@Getter
|
||||
private AccountId id;
|
||||
@Getter private final AccountId id;
|
||||
|
||||
/**
|
||||
* The baseline balance of the account. This was the balance of the account before the first
|
||||
* activity in the activityWindow.
|
||||
*/
|
||||
private Money baselineBalance;
|
||||
@Getter private final Money baselineBalance;
|
||||
|
||||
/**
|
||||
* The window of latest activities on this account.
|
||||
*/
|
||||
@Getter
|
||||
private ActivityWindow activityWindow;
|
||||
@Getter private final ActivityWindow activityWindow;
|
||||
|
||||
/**
|
||||
* Creates an {@link Account} entity without an ID. Use to create a new entity that is not yet
|
||||
* persisted.
|
||||
*/
|
||||
public static Account withoutId(Money baselineBalance, ActivityWindow activityWindow) {
|
||||
public static Account withoutId(
|
||||
Money baselineBalance,
|
||||
ActivityWindow activityWindow) {
|
||||
return new Account(null, baselineBalance, activityWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Account} entity with an ID. Use to reconstitute a persisted entity.
|
||||
*/
|
||||
public static Account withId(AccountId accountId, Money baselineBalance, ActivityWindow activityWindow) {
|
||||
public static Account withId(
|
||||
AccountId accountId,
|
||||
Money baselineBalance,
|
||||
ActivityWindow activityWindow) {
|
||||
return new Account(accountId, baselineBalance, activityWindow);
|
||||
}
|
||||
|
||||
public Optional<AccountId> getId(){
|
||||
return Optional.ofNullable(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total balance of the account by adding the activity values to the baseline balance.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.reflectoring.buckpal.domain;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@@ -66,7 +67,7 @@ public class ActivityWindow {
|
||||
}
|
||||
|
||||
public List<Activity> getActivities() {
|
||||
return new ArrayList<>(this.activities);
|
||||
return Collections.unmodifiableList(this.activities);
|
||||
}
|
||||
|
||||
public void addActivity(Activity activity) {
|
||||
|
||||
@@ -75,7 +75,7 @@ class SendMoneySystemTest {
|
||||
HttpEntity<Void> request = new HttpEntity<>(null, headers);
|
||||
|
||||
return restTemplate.exchange(
|
||||
"/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}",
|
||||
"/accounts/send/{sourceAccountId}/{targetAccountId}/{amount}",
|
||||
HttpMethod.POST,
|
||||
request,
|
||||
Object.class,
|
||||
|
||||
Reference in New Issue
Block a user