はじめに
DDDの実装パターンとして、EntityとValueObjectというものがあります。
ドメイン駆動一般に複雑な抽象論が多い中で、コードに近く一番イメージがつきやすいコード事例として出てくるため、ここだけは何となくわかるぞ!という方もいらっしゃるのではないでしょうか。
今日はこちらの概要とそれぞれの使い道について書きたいと思います。
先にざっくりイメージ図をお伝えすると、こういう図を使って解説します。
何の目的で作るのか?
ドメイン駆動設計は何を解決しようとしているのか
こちらの記事で、ドメイン駆動設計のアプローチはは以下の2ステップがあるということを書きました。
- ドメイン(ソフトウェア化対象の世界)に対して、システムで使うためのモデルを作成する
- モデルをソフトウェア(コード)に落とし込む
DDDでは、このStep2の モデルをコードで表現するためのパターン として、以下の4つを定義しています。
- Entity
- Value Object
- Domain Service
- Domain Event
DDD Refference より一部抜粋 "Express Model With"と書かれている4つ
このうち、 モデルを「オブジェクト(値と振る舞いを持つモノ)」として表現する のがEntityとValue Objectの2つになります。
使用頻度の高さと理解のしやすさから、初学者の方は、まずはこの2つを抑えるのが良いと思います。
DomainServiceとDomainEvent
今回の趣旨からは一瞬それますが、「オブジェクトとして表現しない」残りの二つを軽くだけ説明します。
DomainServiceは、モデルをオブジェクトとして表現すると無理があるものの表現に使います。例えば、集合に対する操作などです。
例として、「予約」というオブジェクト自体があったとします。
"指定された特定の時間帯に予約の空きがあるか?"と尋ねられたとき、その知識を予約オブジェクト自身が答えられる、とモデリングするのは無理があります。自分自身の予約時間を知っていても、それ以外のオブジェクトの状況については情報として持っていないからです。
こういう場合には、DomainSerivceというものを使用します。
ただ、DomainServiceはつい手続き的な書き方になってしまうので、極力EntityとValueObjectで表現できないかを検討して、どうしてもできない時のみ使うようにします。
DomainEventは、「予約が行われた」などの事象をモデリングします。このモデルを別のドメインサービスなどで拾って別の処理を行うといった使い方をします。
ただ、残りの3つに比べるとかなり応用編といったところなので、初学者は一旦は置いておいて後からの理解で大丈夫だと思っています。
それでは、4つのうち今回のテーマではない2つを軽く押さえた上で、EntityとValueObjectの紹介に移りたいと思います。
EntityとValueObjectの違い
同じ「モデルをオブジェクトとして表現するもの」であるのに、なぜ2つあるのでしょうか?その違いさえ理解できれば、使い分けることができるようになるでしょう。
Entity | ValueObject | |
---|---|---|
同一性判定 | 識別子供が同一であれば同一 | 保持する属性が全て同一であれば同一 |
可変性 | 可変 生成されてから、変異するというライフスパンを持つ |
不変 生成されたら、あとは破棄されるのみ |
これが基本的な定義になります。例を挙げて説明します。
Entity
Entityの同一性判定と可変性
社員というEntityについて考えます。
山田さんという社員は、ある会社においては社員番号という識別子123(例)で同一判定され、ます。山田さんは部署が変わろうが、所持金が変わろうが、体重が変わろうが同じ「山田さん」であり、別人にはなりませんよね。
そして、それらの属性が変わるということは、本質的に可変なものである ということです。
一方、新しく名前が同じ山田さんという社員が入ってきて社員番号456が割り振られたとします。この人は部署、所持金、体重が仮に全部同じだったとしても、123の山田さんとは別の人物です。これがEntityの同一性の考え方となります。
ValueObject
ValueObjectの同一性判定
一方、お金について考えます。
2つの10円玉が並んでいて、これを「同じ」と判断したいでしょうか?
それは 文脈、モデリングの目的による ものとなります。
モデリング時の興味が、そのものが表す金銭的価値にしか興味がないとすると、2つの10円玉は同じと考えてよいことになります。
例えば、山田さんが1つ目の10円玉を持っている状況と2つ目のの10円玉を持っている状況では、等しく山田さんのの所持金は10円と考えたいのではないでしょうか。この場合、二つをの10円を区別する必要はありません。このような場合、10円はValueObjectとしてモデリングする方が適切です。
もしこれが造幣局やコインコレクターだとするならば事情は異なるかもしれません。それぞれの10円玉を区別して扱いたい可能性はあり得ます。これが先ほど書いた「文脈、モデリングの目的によるもの」という意味です。
その場合はお金をEntityとしてモデリングすということも検討することになります。
ValueObjectの不変性
山田さんが所持金として10円を持っていたところ、100円に増えたとします。この時に10円玉の数字の10に取り消し線を書いて、100と書き直すでしょうか?いや、そんなことはしませんね。
実際には、10円玉(というValueObject)を、100円玉(というValueObject)と交換するでしょう。10円玉は製造された時に保持する金銭的価値は確定しており、あとからいかなることがあっても変わることはない。これがValueObjectが不変であるということです。
EntityとValueObjectの関係
先ほどの社員とお金のように、Entityが自身の属性としてValueObjectを保持するという関係になるのが基本となります。
Entityの属性は可変ですが、ValueObjectとして持つ属性の値が変わる場合は、ValueObject自体が示す値を変えるのではなく、新しい値を持つValueObjectを生成し、前のValueObjectを置き換えるという使い方します。10円玉を100円玉で置き換えるというのは、まさにこの喩えとなります。
このような設計をする意図
EntityとValueObjectを区別する利点
なぜこのような区別をするのでしょうか?
(これは私個人の解釈になります。)
基本思想としては、プログラミングの一般的なベストプラクティスとして、「不変にできるものは極力不変にした方が良い」というものがあり、モデルの段階からその区別をした方が良い、という理解をしています。
特に気にしなければ、実はValueObjectを使わずに全てEntityとしてモデリングし、実装することは可能であります。ただ、それでは不変にしてもよいところも可変になり、可読性や信頼性を下げてしまう可能性があります。
(「極力不変に」という理由についてはプログラミング一般的な話なのでここであまり掘り下げません。例えばEffectiveJava 第2版の項目15「可変性を最小限にする」にも「不変クラスは誤りにくく、より安全です」とあります。気になる方は読んでみてもらえると良いと思います。)
Primitive型ではなくValueObjectとして設計する利点
ValueObjectについて、Entityとの対比として利点を書きましたが、primitive型と対比すると、きちんと値自体に振る舞いを持たせることで凝集度を上げることが可能になります。
例えば、社員というEntityがメールアドレスを持つ場合、メールアドレスの書式チェックを社員Entityが持つよりも、メールアドレスというValueObjectの生成時のロジック(コンストラクタ等)に書式チェックを持たせる方が、オブジェクトの持つ値と振る舞いの関連度が近いので、凝集度が高いということができます。
【2021/10/30新刊】
DDD解説書の新刊を執筆しました。
重要トピック「モデリング」「集約」「テスト」について詳細に解説し、その他のトピックでは頻出の質問への回答と具体的なサンプルコードをふんだんに盛り込みました。現場で実践して、困っていることがある方はぜひこちらもご覧ください。
また、YouTubeで10分でわかるDDD動画シリーズをアップしています。概要を動画で理解したい方はこちらもどうぞ。チャンネル登録すると新しい動画の通知を受け取ることができます