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