このプロジェクトはDDDの例です。
- お金ドメインモデル
- お金はお金を追加できる
- お金はお金を差し引ける
- お金とお金は比較できる
- 銀行口座ドメインモデル
- 銀行口座から残高を取得できる
- 銀行口座に現金での入金および出金イベントを追加できる
- 銀行口座に別の口座からの入金イベントを追加できる。もしくは、口座から別の口座への出金イベントを追加できる
- 銀行口座の残高は0未満になってはならない
- 銀行口座ドメインサービス
- 銀行口座ドメインサービスは、口座間の送金ができる。
ドメインモデルを反映した例は以下。
- お金はお金を追加できる。
- お金はお金を差し引ける。
Money money1 = Money.of(10000);
Money money2 = Money.of(10000);
Money result = money1.plus(money2).substract(money2);
System.out.println(result);
- お金とお金は比較できる。
Money money3 = Money.of(10000);
Money money4 = Money.of(20000);
Money result = money3.substract(money2);
if (result.isLessThan(Money.zero())) {
System.out.println("result is less than zero!");
}
口座に現金での入金および出金イベントを追加できる。
BankAccount bankAccount = new BankAccount(1L, ImmutableList.of())
.depositCash(Money.of(10000))
.withdrawCash(Money.of(10000));
System.out.println(bankAccount);
口座から残高を取得できる。
BankAccount bankAccount = new BankAccount(1L, ImmutableList.of()).depositCash(Money.of(10000));
System.out.println(bankAccount.getBalance());
口座に別の口座からの入金イベントを追加できる。もしくは、口座から別の口座への出金イベントを追加できる。
public final class BankAccountService {
public static TransferResult transfer(BankAccount to, BankAccount from, Money money) {
BankAccount updatedFrom = from.withdrawTo(to, money);
BankAccount updatedTo = to.depositFrom(from, money);
return new TransferResult(updatedTo, updatedFrom);
}
}
口座の残高は0未満になってはならない
@Value
public final class BankAccount {
// ...
public static BankAccount of(Long id, List<BankAccountEvent> events) {
if (getBalanceByEvents(events).isLessThan(BigDecimal.ZERO)) {
throw new IllegalArgumentException("total money is less than zero!");
}
return new BankAccount(id, events);
}
// ...
}
銀行口座ドメインサービスは、口座間の送金ができる。
Money initialMoney = Money.of(10000);
BankAccount bankAccount1 = BankAccount.of(IdGenerator.generateId()).depositCash(initialMoney);
BankAccount bankAccount2 = BankAccount.of(IdGenerator.generateId()).depositCash(initialMoney);
System.out.println("bankAccount1 = " + bankAccount1.getBalance());
System.out.println("bankAccount2 = " + bankAccount2.getBalance());
BankAccountService.TransferResult transferResult = BankAccountService.transfer(
bankAccount1,
bankAccount2,
Money.of(10000)
);
System.out.println("to = " + transferResult.getTo().getBalance());
System.out.println("from = " + transferResult.getFrom().getBalance());
この例は圧倒的に詳細です。メンタルモデルがないため、コードは理解しがたい。 ドメインモデルが変更された場合、コードを変更することも難しい。
public static Money getBalance(BankAccount bankAccount){
BigDecimal totalAmount = BigDecimal.ZERO;
for(BankAccountEvent event : bankAccount.getEvents()) {
totalAmount = totalAmount.add(event.getMoney().getAmount());
}
Money money = new Money();
money.setCurrency(bankAccount.getEvents().get(0).getMoney().getCurrency());
money.setAmount(totalAmount);
return money;
}
Money baseMoney = new Money();
baseMoney.setCurrency(Currency.getInstance("JPY"));
baseMoney.setAmount(BigDecimal.valueOf(10000));
BankAccount bankAccount1 = new BankAccount();
bankAccount1.setId(IdGenerator.generateId());
List<BankAccountEvent> events1 = Lists.newArrayList();
BankAccountEvent incrementEvent1 = new BankAccountEvent();
incrementEvent1.setId(IdGenerator.generateId());
incrementEvent1.setToBankAccountId(bankAccount1.getId());
incrementEvent1.setFromBankAccountId(null);
incrementEvent1.setMoney(baseMoney);
incrementEvent1.setOccurredAt(ZonedDateTime.now());
events1.add(incrementEvent1);
bankAccount1.setEvents(events1);
BankAccount bankAccount2 = new BankAccount();
bankAccount2.setId(IdGenerator.generateId());
List<BankAccountEvent> events2 = Lists.newArrayList();
BankAccountEvent incrementEvent2 = new BankAccountEvent();
incrementEvent2.setId(IdGenerator.generateId());
incrementEvent2.setToBankAccountId(bankAccount1.getId());
incrementEvent2.setFromBankAccountId(null);
incrementEvent2.setMoney(baseMoney);
incrementEvent2.setOccurredAt(ZonedDateTime.now());
events2.add(incrementEvent2);
bankAccount2.setEvents(events2);
Money totalAmount1 = getBalance(bankAccount1);
Money totalAmount2 = getBalance(bankAccount2);
System.out.println("bankAccount1 = " + totalAmount1);
System.out.println("bankAccount2 = " + totalAmount2);
Money data = new Money();
data.setCurrency(bankAccount1.getEvents().get(0).getMoney().getCurrency());
data.setAmount(BigDecimal.valueOf(10000));
BankAccountService.Result result = BankAccountService.moveData(
bankAccount1,
bankAccount2,
data
);
Money newTotalAmount1 = getBalance(result.getTo());
Money newTotalAmount2 = getBalance(result.getFrom());
System.out.println("toTotalAmount = " + newTotalAmount1);
System.out.println("fromTotalAmount = " + newTotalAmount2);
public class BankAccountService {
@Data
public static class Result {
private BankAccount to;
private BankAccount from;
}
public static Result moveData(BankAccount to, BankAccount from, Money money) {
BigDecimal totalAmount = BigDecimal.ZERO;
for(BankAccountEvent event : to.getEvents()) {
totalAmount = totalAmount.add(event.getMoney().getAmount());
}
if (totalAmount.compareTo(money.getAmount()) < 0) {
throw new IllegalArgumentException("total money is less than zero!");
}
BankAccountEvent decrementEvent = new BankAccountEvent();
decrementEvent.setId(IdGenerator.generateId());
decrementEvent.setFromBankAccountId(from.getId());
decrementEvent.setToBankAccountId(to.getId());
Money negated = new Money();
negated.setCurrency(money.getCurrency());
negated.setAmount(money.getAmount().negate());
decrementEvent.setMoney(negated);
decrementEvent.setOccurredAt(ZonedDateTime.now());
BankAccount newFrom = new BankAccount();
List<BankAccountEvent> newFromEvent = Lists.newArrayList(from.getEvents());
newFromEvent.add(decrementEvent);
newFrom.setId(from.getId());
newFrom.setEvents(newFromEvent);
BankAccountEvent incrementEvent = new BankAccountEvent();
incrementEvent.setId(IdGenerator.generateId());
incrementEvent.setFromBankAccountId(from.getId());
incrementEvent.setToBankAccountId(to.getId());
incrementEvent.setMoney(money);
incrementEvent.setOccurredAt(ZonedDateTime.now());
BankAccount newTo = new BankAccount();
List<BankAccountEvent> newToEvent = Lists.newArrayList(to.getEvents());
newToEvent.add(incrementEvent);
newTo.setId(to.getId());
newTo.setEvents(newToEvent);
Result result = new Result();
result.setFrom(newFrom);
result.setTo(newTo);
return result;
}
}