Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

ホワイトプラスのエンジニアの@hachihiro224です。
この記事は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!!しております。

yamakii
wh-plus
「日々の生活と心にゆとりと豊かさ」を生むためのサービスを提供している会社です。
https://www.wh-plus.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした