22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WHITEPLUSAdvent Calendar 2017

Day 15

Goでドメイン駆動設計のモデルを書く

Last updated at Posted at 2017-12-14

ホワイトプラスのエンジニアの@yamakiiです。
この記事はWHITEPLUS Advent Calendar 2017 15日目になります。

普段はPHPでドメイン駆動設計(DDD)をしているのですが、勉強の意味も兼ねて今回はGoでDDDのモデルを書いてみました。

題材は最近t_wadaさんの新訳がでて話題のテスト駆動開発の第一章になります。

DDDでモデルを作る際のポイントは実際に業務で利用されている言葉でモデルを進めていくことです。これをGo言語の「型」をうまく使いながら実践できればと思っています。

第一章の内容を全て網羅すると長くなってしまいますので、「通貨の交換をする」機能に絞ってモデリングしてみます。

役者としては

  • 通貨を表すCurrency
  • お金を表すMoney
  • 交換レートのRate
  • お金同士の交換を司るBank
    でモデリングしてみます。

まず最初に「Currency」型と「Money」型を作っていきます。

// 通貨
type Currency string

// 例としてドルとユーロ
const (
	USD = Currency("USD")
	EUR = Currency("EUR")
)

// 通貨からMoneyを生成する
func (c Currency) New(a int) Money {
	return Money{a, c}
}

// お金
type Money struct {
	amount   int
	currency Currency
}

// 5ドルのお金はこうなる
five := USD.New(5)

ここでは通貨(Currency)を定義して、そこからMoneyを生成するというシナリオを書いています。

次に「Rate」型と「Bank」型を作っていきます。
Bankはお金の交換を司ると言いましたが、ここでは次の機能を作ってみます。

  • 交換レートの追加
  • 交換レートの参照
// 交換レート
type Rate int

// 通貨のペア(交換の方向も持つ)
type pair struct {
	from Currency
	to   Currency
}

// 銀行(通貨のペア毎に交換レートを持つ)
type Bank struct {
	rates map[pair]Rate
}

// 交換レートの追加
func (b *Bank) AddRate(from Currency, to Currency, rate Rate) {
	b.rates[pair{from, to}] = rate
}

// 交換レートの参照
func (b Bank) Rate(from Currency, to Currency) Rate {
	if rate, ok := b.rates[pair{from, to}]; ok {
		return rate
	}
	return Rate(1)
}

// Bankの生成
func NewBank() Bank {
	return &Bank{map[pair]Rate{}}
}

ここではRateの管理のキーとしてpair型を利用しています。
これをmapのキーとすることで、交換レートの参照はスッキリとしたコードになっています。

いよいよ通貨の交換です。

  • 交換率の参照はBank
  • 通貨交換はMoney

とそれぞれに機能を分けて実装してみます。

// Bankの交換は交換率を取得し、Moneyへ委譲
func (b Bank) Reduce(m Money, to Currency) Money {
	rate := b.Rate(m.currency, to)
	return m.Reduce(rate)
}

// Moneyは指定されたRateに応じて必要なMoneyを算出
func (m Money) Reduce(rate Rate) Money {
	m.amount = m.amount / int(rate)
	m.currency = to
	return m
}

最終的な利用イメージはこんな感じになります。

test.go
func TestReduceMoneyDifferentCurrency(t *testing.T) {
	bank := NewBank()

	// 交換レート追加(EUR : USD = 1 : 2)
	bank.AddRate(EUR, USD, Rate(2))

	// 2ユーロをドルに通貨交換
	actual := bank.Reduce(EUR.New(2), USD)

	// 1ドルになるはず
	if expected := USD.New(1); expected != actual {
		t.Fatalf("Expected %#v Got %#v", expected, actual)
	}
}

ざっと書いてみましたが、こんなツッコミが出るかもしれません。

  • pairとrateをあわせてExchangeRate(交換レート)とすべき
  • pairはドメインの言葉じゃないので、そのままの意味のkeyにしては?

他にもいろいろあると思いますが、こんな感じで話をしながらモデルを洗練していくとよいかなと思います。

今回Go言語で実践した感想ですが、Goの型はクラスに比べると簡単に宣言できるので、気持ちよくモデリングすることができました。
特にvalue objectのパターンを多用する場合には利用し易いという印象を持ちました。


ホワイトプラスではエンジニアを募集しています
ホワイトプラスでは、web×リアル領域で「新しい日常を創りたい」エンジニアWanted!!しております。

22
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?