little hands' lab

ドメイン駆動設計を布教したい

DDD基礎解説:Entity、ValueObjectってなんなんだ

このエントリは ドメイン駆動設計 #2 Advent Calendar 2018 の9日目です。
8日目は @mafuyuk さんのDDD本3章を読んでこのように理解しましたでした。
10日目は @bigwheel さんの 『「集約の境界と整合性(略」に対して頂いたアイデアの分類と現状での僕の回答らしきもの』 です。

はじめに

DDDの実装パターンとして、EntityとValueObjectというものがあります。
ドメイン駆動一般に複雑な抽象論が多い中で、コードに近く一番イメージがつきやすいコード事例として出てくるため、ここだけは何となくわかるぞ!という方もいらっしゃるのではないでしょうか。
今日はこちらの概要とそれぞれの使い道について書きたいと思います。

先にざっくりイメージ図をお伝えすると、こういう図を使って解説します。

f:id:little_hands:20181210091851p:plain

何の目的で作るのか?

ドメイン駆動設計は何を解決しようとしているのか
こちらの記事で、ドメイン駆動設計のアプローチはは以下の2ステップがあるということを書きました。

  1. ドメイン(ソフトウェア化対象の世界)に対して、システムで使うためのモデルを作成する
  2. モデルをソフトウェア(コード)に落とし込む

DDDでは、このStep2の モデルをコードで表現するためのパターン として、以下の4つを定義しています。

  1. Entity
  2. Value Object
  3. Domain Service
  4. Domain Event

DDD Refference より一部抜粋 "Express Model With"と書かれている4つ
f:id:little_hands:20181209103218p:plain

このうち、 モデルを「オブジェクト(値と振る舞いを持つモノ)」として表現する のが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(例)で同一判定され、ます。山田さんは部署が変わろうが、所持金が変わろうが、体重が変わろうが同じ「山田さん」であり、別人にはなりませんよね。
そして、それらの属性が変わるということは、本質的に可変なものである ということです。

f:id:little_hands:20181209154718p:plain:w400

一方、新しく名前が同じ山田さんという社員が入ってきて社員番号456が割り振られたとします。この人は部署、所持金、体重が仮に全部同じだったとしても、123の山田さんとは別の人物です。これがEntityの同一性の考え方となります。

f:id:little_hands:20181209154821p:plain:w400

ValueObject

ValueObjectの同一性判定

一方、お金について考えます。

2つの10円玉が並んでいて、これを「同じ」と判断したいでしょうか?

それは 文脈、モデリングの目的による ものとなります。
モデリング時の興味が、そのものが表す金銭的価値にしか興味がないとすると、2つの10円玉は同じと考えてよいことになります。
例えば、山田さんが1つ目の10円玉を持っている状況と2つ目のの10円玉を持っている状況では、等しく山田さんのの所持金は10円と考えたいのではないでしょうか。この場合、二つをの10円を区別する必要はありません。このような場合、10円はValueObjectとしてモデリングする方が適切です。

f:id:little_hands:20181209125523p:plain:w400

もしこれが造幣局やコインコレクターだとするならば事情は異なるかもしれません。それぞれの10円玉を区別して扱いたい可能性はあり得ます。これが先ほど書いた「文脈、モデリングの目的によるもの」という意味です。
その場合はお金をEntityとしてモデリングするということも検討することになります。

ValueObjectの不変性

山田さんが所持金として10円を持っていたところ、100円に増えたとします。この時に10円玉の数字の10に取り消し線を書いて、100と書き直すでしょうか?いや、そんなことはしませんね。

f:id:little_hands:20181209153927p:plain:w400

実際には、10円玉(というValueObject)を、100円玉(というValueObject)と交換するでしょう。10円玉は製造された時に保持する金銭的価値は確定しており、あとからいかなることがあっても変わることはない。これがValueObjectが不変であるということです。

f:id:little_hands:20181209154300p:plain:w400

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の生成時のロジック(コンストラクタ等)に書式チェックを持たせる方が、オブジェクトの持つ値と振る舞いの関連度が近いので、凝集度が高いということができます。

もっと詳しく知りたい方は

little-hands.booth.pm

初めてDDDを学ぶ方、もしくは実際に着手して難しさにぶつかっている方向けの書籍を出しました。
迷子になりがちな「DDDの目的」や「モデル」の解説からはじめ、 具体的なモデリングを行い実装まで落とす事例を元に、DDDの魅力や効果を体感することを目指します。

この本の「第6章 ドメイン層の実装」では、エンティティ、値オブジェクト以外のオブジェクトについても、それぞれ詳しく解説しています。
よろしければお求めください。

Twitterでも、DDDに関して発信したり、「質問箱」というサービスを通じて質問を受け付けています。こちらもよろしければフォローしてください。

松岡@技術書典8Day2え28 / DDDブログ書いてます (@little_hand_s) | Twitter