TL;DR

  • 「自分の口座から別の口座に送金する」ユースケースをオブジェクト指向でモデリングし、口座を表す Account に、引き出す withdraw と預け入れる deposit を定義する。
  • 口座残高は、基準日時点の残高に基準日以降の取引を反映して算出する。

内容

口座のモデル

送金のユースケースをオブジェクト指向の視点でモデリングすると、口座を表す Account クラスを作り、指定した金額を引き出す withdraw と、指定した金額を預け入れる deposit の 2 つのメソッドを定義することになる。

以下は、本書のサンプルを Go で書き直したものである。

// buckpal/application/domain/model/account.go
type Account struct {
	id              *AccountID
	baselineBalance Money
	activityWindow  ActivityWindow
}

Account は口座を表す。id はこのオブジェクトを一意にする値、baselineBalance は基準日の開始時点の口座残高、activityWindow は基準日以降の取引履歴である。すべての取引をメモリに持つとリソースが足りなくなるので、基準日以降の取引だけを持つ。そして、baselineBalance に基準日以降のすべての取引(activityWindow)を反映することで、現時点の残高を算出する。

ActivityWindow は取引履歴を表し、内部では Activity のスライスで持つ。Activity は、口座に対する引き出しや預け入れの取引を表す。

メソッド

// buckpal/application/domain/model/account.go
func (a *Account) Withdraw(money Money, targetAccountID AccountID) bool {
	if !a.mayWithdraw(money) {
		return false
	}
 
	withdrawal := NewActivity(
		*a.id,
		*a.id,
		targetAccountID,
		time.Now(),
		money,
	)
	a.activityWindow.AddActivity(*withdrawal)
 
	return true
}
 
func (a *Account) mayWithdraw(money Money) bool {
	return AddMoney(a.CalculateBalance(), money.Negate()).IsPositiveOrZero()
}
 
func (a *Account) Deposit(money Money, sourceAccountID AccountID) bool {
	deposit := NewActivity(
		*a.id,
		sourceAccountID,
		*a.id,
		time.Now(),
		money,
	)
	a.activityWindow.AddActivity(*deposit)
 
	return true
}

Deposit は、預け入れの取引を activityWindow に追加する。Withdraw は、引き出しの取引を activityWindow に追加する。このとき、「送金元の口座は、自分の口座残高を超える金額を送金してはいけない」というビジネスルールをチェックする。

キーワード

自分の理解

AccountID は ドメイン駆動設計でいう値オブジェクトに、Account、ActivityWindow、Activity はエンティティにあたる。

基準日時点の残高に以降の取引を積み上げて今の残高を出す仕組みは、Git やイベントソーシングのようなイメージだと思った。