ちまたで話題だったValueObjectの話。
目次
- ValbuObjectじゃなくて、valueが難しい
- ValueObjectを噛み砕く
- ValueObjectはいつ使うの(とくにDDD)
- 具体例で考えるvalueの難しさ
ValbuObjectじゃなくて、valueが難しい
ValueObjectの話として考えるとすごく混乱するのですが、"value"の定義がブレてると考えると分かりやすくなるかもです。
value/Valueは二つ、定義できる
- 問題領域の抽象化された値(value)
- 値(Value)と参照(Object)の対比
と、分けられます。この記事では、小文字だけのvalueは、前者を指すようにします。
※ 言葉の普遍性からもっと色んな定義もあるだろうけどフォーカスアウト
問題領域の抽象化された値(value)
問題領域、problem domain、所謂、ドメインと呼ばれるそれです。定義は難しいのでおいて、実例で語ります。
"出席番号"を格納する変数を考えてみましょう。
int number;
この表現には、難癖が付けられます。
- 出席番号に、0とか-1とかはありえない。
- 出席番号に、10000とかありえない。
問題領域の値(value)とは、自然に存在する値ではなく、対象に合わせ"抽象化"された値です。プログラミング言語が備えてる基本的な型では、表現が出来ません(たまたま重なることはある)。このようなズレは昔からごくごく当たり前です。概念/詳細、設計/実装など対比構造で分けられて、大きな範囲で区別すると問題領域と解決領域と分け、乖離するのが当たり前でした。
動けばよいから卒業したプログラマは、この表現の違いへの入門が始まったとともいえる気がします。どう表現するか悩まされ続ける無間地獄にようこそ。
最近のトレンドをざっくり言えば、抽象データ型や型システムなりの表現を利用して、問題領域に寄せると良い感じになるんじゃねえの?設計と実装が乖離しなくて。でしょうか。
値(Value)と参照(Object)の対比構造
こちらはプログラムの実現手段の文脈になります。プログラミング言語に入門する際に、確実にやってる奴です。参照渡しとかでググってください。この文脈のValueとは、先ほどのvalueとは異なり、一定の性質を持つ記憶領域のことになります。その性質の1つがValueObjectで語られる等価性だったりします。
値と参照という対比構造の上にある概念で、値をValue、参照をObjectとして呼ぶことも多いです。
最適化が重要な実際のデータ記憶領域と、抽象化が重要な高水準言語のデータ表現との間に生まれた溝とも言える気がします。
(抽象化という文脈で、小文字valueとつながりそうですが、私の力量では説明できません。)
ValueObjectを噛み砕く
一般的には二つの定義がされることが多いです。
- Pofeaaで、ファウラーが定義した。ValueObject
- DDDで、エヴァンスが"利用"した。ValueObject
valueとValueの違いというコンテキストがあれば、この定義が嚙み砕きやすくなるかと思います。
ファウラーのValueObjectは、Objectの表現手段
ファウラーのValueObjectのValueとは、ValueとObjectの対比構造のValueです。
等価性等は、Object/Valueの対比でのValueが持つ性質の話です。
「Objectでも、Valueのようにふるまえると便利なこと多いよねー」ってだけ。
class ValueObject implements Value {}
Object as Value. 対象はObjectで、その表現の話。
DDDのValueObjectは、valueの表現に、ValueObjectを利用
エヴァンスのValueObjectと、ファウラーのValueObjectを、分けて定義されることがありますが、実際は同じものだと私は考えます。エヴァンスが明らかにソフトウェアアーキテクチャの文脈を意識してる(EntitiyやRepository)ことと、ファウラーがDDDのValueObjectを分けてないことを考えてる辺り。文献探すのだるい。
では、なぜ皆が混乱したかというと、エヴァンスの主張と、世に広まった主張がちょっとズレた。に限るかと。
エヴァンスは「問題領域(domain)のvalueを、ValueObjectで表現すると良いんじゃねー」と言いたかったのです。
世に広まったのは「ValueObjectで問題領域(domain)を表現しよう」となりました。ぶっちゃけ、そうエヴァンスが書いてる気がします。さらに「Entity(参照)とValueObject(値)という対比」がDDD内にもありValue/Objectの対比にもかかり、混乱に拍車をかけています。
無理にコード表現するならこれ
//小文字valueをDomainValueとした
class DomainValue extends ValueObject {}
このextendsは、どっちかつうとuse的な感じ。
対象は"value" で、その表現の話。
そのため、「ValueObjectを"使う"と良いんだぜー」だけだと、言い方が"少し"違ったわけです。
ようするに
エリックエヴァンスのドメイン駆動は、コードの実例があってデザインパターンと勘違いしやすい上に、問題領域(ドメイン)という分かりそうで分からん超ハイコンテキストな上で語られてるので、空気読む必要がある。
さらにvalueも文脈で色々あって、そこがずれてた。という話でした。
以下、おまけです。
ValueObjectはいつ使うの(とくにDDD)
ここまでの流れで、言い換えれば、「どこまで問題領域のvalueを厳密に表現するべきか。」となります。
問題領域に寄せるのか解決領域に寄せるのかのトレードオフが、そのまま適用されます。
傾向としては、以下な感じでしょうか。
- 問題領域に寄せる -> 実装や速度にオーバーヘッドが増える。安全性が高い。
- 解決領域に寄せる -> シンプルでオーバーヘッドが減る。安全性が低い。
さらには
- DDDでのアプリケーション実装の側面だと、ValueObjectが最も利便性高く持ち上げられる。モダンUtil的。
- 実装のオーバーヘッドは、可読性を損なうか損なわないか判断が割れる。
- 手段に走るとカプセル化の粒度を間違いやすく、逆に問題領域から乖離することがある。
- 問題領域に欲しいのは、型制約持ちのvalueであり、ただのタイプセーフなことも。
と、議論に広がりがあります。
最近では、処理のオーバーヘッドを比較的軽視出来る分野がメインストリームになり、関数型言語トレンドから来た宣言性や型安全の期待、マイクロサービスのようにシステム全体が複雑化する対処として、個々のコンポーネントは凝集が高く安全性を求める傾向があり、安全性側に寄せたくなる背景もあります。
指針は?
個人的な考えです。
ドメイン駆動設計でのドメイン層は「問題領域の抽象化された値(value)」というのは重要(にすべき)です。積極的に表現するべきだと考えます。
表現方針は、問題領域での従属/独立性によって分けます。従属性が高いならその関係でまとめれば良いし、独立性が高いならそれ一つでまとめるだけです。一つの値とか複数の値とかは、既に表現内容なので、出来るだけ判断に使わないように頑張る。もちろん当初は曖昧な概念になるものもありますが、共通認識を決着させた上で、今後の変化に追従する必要があります。また、独立性が、一意性とデータのライフサイクルを起因としてるとEntity(DDD)になっていきます。
まとめると、問題領域に向かい合って悩んで決めて共通の認識を作るのが大切なので積極的に"value"の表現を検討するべきです。絶対に使えでも必要になったら使いなさい。でも、考えを放棄してしまっている気がします。
逆にインフラ層では、ライブラリやフレームワークや外部連携が注目領域です。そこで相性の良いシンプルなデータ型を素直に表現に使うことが多くなります。ドメイン層で、問題領域の安全性が担保されてるからこそ、インフラ層では注目領域を明確に切り替えられるので、アプリケーションの全体の安全性が担保しやすい。というのがDDDの良さに感じます。問題領域への向き合い方を限定的にすることで、トレードオフのバランスをとってるとも言えるでしょうか。
そもそも難しい
ただし、DDDの問題領域(ドメイン)は難しいです。アプリケーションは、そもそもビジネス的な問題領域への解決領域として用意されています。アプリケーションの問題領域とビジネスの問題領域は、似てるが異なります。実践DDDではサブドメインとコンテキストというアイディアで乗り切ろうとしてますが、エリック本のドメインエキスパートって誰だよ感がでちゃうとかとか。
例えば、「そのvalueは、アプリに関係ないビジネス制約だったりしない?逆に、ただのIFの制約だったりしない?」等で、判断がブレます。
一律の指針というのは、難しいでしょう。問題領域の定義やその表現方法というのは、このアプリケーションが何なのかと考える作業そのものであり、それがDDDともいえるわけです。つらい。。
これが逆に指針の渇望になり、欲求として逆にあらわれてしまうのかもしれません。マナーというよりは、ルールが無くて悩んでる人が多いほうが個人的には感じます。
具体例で考えるvalueの難しさ
指針の材料出しに、ValueObjectを導入するかという視点の具体例を出して終わりたいと思います。
例えば「注文の備考」は、どうするか考えてみましょう。
- A「注文のただの文字列属性」->
Order { String note}
- B「汎用的な"備考"というvalueが注文の属性になってる」->
Order { Note note }
- C「独立した"注文の備考というvalue"」->
OrderNote
と考えられます。
それらを判断する材料も様々にあります。
- 「全ての備考には、個人情報が含まれてたら記録しないで欲しい。」
- 「備考は、DBMS上であふれると困るから」
- 「備考」ってそこまで重要だっけ?
等々あるわけです。
個人的には、前述のように独立性の明暗で分けることが多いです。一般的な"備考"として考えると、注文に従属する属性であると捉えます。"備考"という汎用的な表現が多用されていて制約等がある場合は、"備考"として独立します。Cはかなり固有なことが無ければ懐疑的で、AかBになりやすいでしょうか。みなさまどうでしょう。
というわけで、ValueObjectの前に、valueの捉え方があり、さらにそれを表現に落とすかしないか。が難しいのです。
なので、ValueObjectの是非ではなく、問題領域のvalueの扱いって難しいね。という話でした。
参考記事とか
TODO