<aside> 💡 앞에서 논의한 아키텍처를 어떻게 실제 코드로 구현할지 살펴보자!
</aside>
애플리케이션, 웹, 영속성 계층이 아주 느슨하게 결합되어 있기 때문에, 필요한 대로 도메인 코드를 모델링할 수 있다.
DDD를 할 수도 있고, 풍부하거나 빈약한 도메인 모델을 구현할 수 있다.
육각형 아키텍처는 도메인 중심의 아키텍처에 적합하기 때문에, 도메인 엔티티를 만드는 것으로 시작한다.
한 계좌에서 다른 계좌로 송금하는 유스케이스를 구현해보자.
Account
엔티티를 만들고 출금 계좌에서 돈을 출금해서 입금 계좌로 돈을 입금하는 것이다.package io.reflectoring.buckpal.account.domain;
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Account {
@Getter
private final AccountId id;
@Getter
private final Money baselineBalance;
@Getter
private final ActivityWindow activityWindow;
public static Account withoutId(
Money baselineBalance,
ActivityWindow activityWindow) {
return new Account(null, baselineBalance, 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);
}
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())
.isPositiveOrZero();
}
public boolean deposit(Money money, AccountId sourceAccountId) {
Activity deposit = new Activity(
this.id,
sourceAccountId,
this.id,
LocalDateTime.now(),
money);
this.activityWindow.addActivity(deposit);
return true;
}
@Value
public static class AccountId {
private Long value;
}
}
Account
엔티티는 실제 계좌의 현재 스냅숏을 제공한다.
Activity
엔티티에 포함된다.Account
엔티티는 ActivitiyWindow
값 객체에서 포착한 지난 며칠 혹은 몇 주간의 범위에 해당하는 활동만 보유한다.계좌의 현재 잔고를 계산하기 위해서 Account
엔티티는 활동창의 첫 번째 활동 바로 전의 잔고를 표현하는 baselineBalance
속성을 가지고 있다.