애플리케이션, 웹, 영속성 계층이 현재 아키텍처에서 느슨하게 결합되어 있기 때문에 필요한 대로 도메인 코드를 자유롭게 모델링할 수 있다.
한 계좌에서 다른 계좌로 송금하는 유스케이스를 구현.
객체지향적인 방식으로 모델링하는 한 가지 방법은 Account 엔티티를 만들고 출금 계좌에서 돈을 출금 후 입금 계좌로 돈을 입금하는 것이다.
package buckpal.domain;
public class Account
private AccountId id;
private Money baselineBalance; // 첫 번째 활동 바로 전의 잔고
private ActivityWindow activityWindow; // 활동창
public Money calculateBalance(){
return Money.add(
this.baselineBalance,
this.activityWindow.calculateBalance(this.id));
}
// 출금
public boolean withdraw(Money money, AccountId targetAccountId){
if (!mayWithdraw(money)) return false;
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())
.isPositive();
}
// 입금
public boolean deposit(Money money, AccountId sourceAccountId) {
Activity deposit = new Activity(
this.id,
sourceAccountIdm
this.id,
LocalDateTime.now(),
money);
this.activityWindow.addActivity(deposit);
return true;
}
// getter, setter
}
이제 입금과 출금을 할 수 있는 Account 엔티티가 있으므로 이를 중심으로 유스케이스를 구현하기 위해 바깥 방향으로 나아갈 수 있다.
일반적으로 유스케이스는 다음과 같은 단계를 따른다.
유스케이스는 인커밍 어댑터로부터 입력을 받는다. (입력 유효성 검증은 다른 곳에서 처리)
비즈니스 규칙을 충족하면 유스케이스는 입력을 기반으로 어떤 방법으로든 모델의 상태를 변경한다. 일반적으로 도메인 객체의 상태를 바꾸고 영속성 어댑터를 통해 구현된 포트로 이 상태를 전달해서 저장될 수 있게 한다. 유스케이스는 또 다른 아웃고잉 어댑터를 호출할 수도 있다.
마지막 단계는 아웃고잉 어댑터에서 온 출력값을, 유스케이스를 호출한 어댑터로 반환할 출력 객체로 변환하는 것이다.
넓은 서비스 문제를 피하기 위해 모든 유스케이스를 분리된 각각의 서비스로
package buckpal.application.service;
@RequiredArgsConstructor
@Transactional
public class SendMoneyService implements SendMoneyUseCase {
private final LoadAccountPort loadAccountPort;
private final AccountLock accountLock;
private final UpdateAccountStatePort updateAccountStatePort;
@Override
public boolean sendMoney(SendMoneyCommand command) {
// TODO: 비즈니스 규칙 검증
// TODO: 모델 상태 조작
// TODO: 출력 값 반환
}
}