![見出し画像](https://assets.st-note.com/production/uploads/images/79091029/rectangle_large_type_2_5f277932e95093d7a22022e93daf1696.jpeg?width=1200)
天下一バリューオブジェクト牛丼談義
どうやらバリューオブジェクト(値オブジェクト)がホットな話題らしい。
今も熱い牛丼談義が続いている。僕も自転車置場で牛丼はつゆだくがうまいとか、卵は半熟か生かどうかとか話すのは大好きだ。僕は最近、大盛りを食べるのはちょっときつくなってきた。それはともかく、せっかくだから僕もバリューオブジェクトの天下一牛丼談義に参戦しよう。
天下一牛丼談義への参戦のきっかけは、かとじゅんさんがバリューオブジェクトはドメイン固有型の一種であるという解釈を記事で紹介していて、それは違うんじゃないかと思ってtweetをしたことだ。これにはかとじゅんさんから回答をもらうことができた。しかし、まだいくつかの疑問が残っているので、ドメイン固有型とドメインオブジェクトとはなにかという議論を通じて、バリューオブジェクトと不変条件、そしてバリューオブジェクトとは結局は何であるのかというのを、ここで整理していきたい。
バリューオブジェクトはドメイン固有の型なのか?
ドメイン固有型(値オブジェクト含む)を再考するという記事の中で、かとじゅんさんは次のようにバリューオブジェクトについての言及している。
これは僕の解釈です。値オブジェクトはドメイン固有型の一種です。なので、不変と等価判定だけではなく、なにかしらのドメイン固有の不変条件(invariant)を維持する責任があると考えます(もちろん型として切り出すわけですからその投資に見合うだけの見返りがないといけません)。
ここでドメイン固有型は、記事内でも参照されている97 Thingsのプリミティブ型よりドメイン固有の型というエッセイのドメイン固有の型(Domain-Specific Types)のことのようなので、DateやPointのようなバリューオブジェクトについても、ドメイン固有の型とするのは矛盾していると思ったのでtweetをした。
PofEAAやDDDの説明だとIDに基づかない等価性を持つオブジェクトなので、特定のドメインに依存しない汎用的なDateやPointも値オブジェクトだけど、記事の値オブジェクトの解釈とは矛盾しないのかな?
— コダック (@ryoaita) May 17, 2022
ドメイン固有型(値オブジェクト含む)を再考する - かとじゅんの技術日誌 https://t.co/gcjvAugxD1
これには次のように回答をもらえた。
DateやPointが扱う問題ドメインがあるはずなので、矛盾はしないと考えています。
— かとじゅん (@j5ik2o) May 17, 2022
それでもDateやPointをドメイン固有の型と言うのは難しいと思ったので、さらに次のような質問をして、回答ももらえた。回答によると97 Thingsのドメイン固有の型の一種はなく、DDDのドメインオブジェクトの一種という解釈のようだ。
97 Thingsのドメイン固有の型やDDDのドメインオブジェクトは、自分が開発対象とするドメイン固有の知識を型やオブジェクトの実装に活かすための方法論なので、自分のドメインのスコープの外で別のドメインに属していても、それをドメイン固有の型とは呼べないのではないでしょうか?
— コダック (@ryoaita) May 19, 2022
確かに固有型という言い方は誤解を招いたかもしれません。言いたいことはドメインオブジェクトの型であるという意図でした。他人のドメインはドメインの定義に含まれるのかが論点ですね。Evansは汎用的なドメインのライブラリを作っていたので肯定派だと思ってます。正確なところがわかればいいですが
— かとじゅん (@j5ik2o) May 19, 2022
まずは、バリューオブジェクトは97 Thingsのドメイン固有の型ではないという理解で良いだろう。
ドメインオブジェクトとはなにか?
ドメインオブジェクトが自分が関わらない他人のドメインのオブジェクトを含むかどうかについては、他人のドメインについて言及するのは混乱を招くので避けるべきだし、用語を作る側を想定すると、そのような定義はしないのでは思うので、かとじゅんさんと僕の認識は異なる。
しかし、そもそもDDDのドメインオブジェクトとはなんだろう?実はよくわからない。なぜならEric Evans氏による明確な定義がどこを探しても見つからないのだ。
DDDの原典であるエリック・エヴァンスのドメイン駆動設計(DDD本)の第4章では次のような記述が見つける。
複雑なプログラムはレイヤに分割すること。各レイヤで設計を進め、凝集度を高めて下位層だけに依存するようにすること。標準的なアーキテクチャパターンに従って、上位のレイヤに対しては疎結合にすること。ドメインモデルに関係するコード全部を1つの層に集中させ、ユーザインタフェース、アプリケーション、インフラストラクチャのコードから分離すること。表示や格納、アプリケーションタスク管理などの責務から解放されることで、ドメインオブジェクトはドメインモデルを表現するという責務に専念できる。これによって、モデルは十分豊かで明確になるように進化し、本質的なビジネスの知識をとらえて、それを機能させることができるようになる。
正直イマイチよくわからないが、ドメインオブジェクトはドメインモデルを表現する責務を負うらしい。結局、DDD本ではドメインオブジェクトという用語は何度も書籍内で使われているが、「ドメインオブジェクトとはXである」というような明確に定義だとわかる記述はDDD本では見当たらない。
DDD本が難解なせいか、Eric Evans氏による別冊のリファレンスが出版されていて、PDFが無料で配布されている。このPDFを開くと表示されるタイトルが Microsoft Word - pdf version of final doc - Mar 2015.docx なのも、なかなかに味わい深い。しかし、やはりドメインオブジェクトの定義は見つからない。ドメインの定義はあるので代わりに参照しよう。
A sphere of knowledge, influence, or activity. The subject area to which the user applies a program is the domain of the software.
ソフトウェアのユーザーがプログラムを利用するときに対象とする領域をドメインと呼ぶようだ。DDDのドメインオブジェクトは、おそらく、ドメイン、あるいは、ドメインモデルを構成するオブジェクトのことを指す明確な定義がない緩い用語なのだろう。DDDのコンテキストで用語が何を意味するかという疑問に対して、定義ではなくそれぞれの解釈が返ってくるのは、どうやらDDD本の記述に理由がありそうだ。
ちなみに、Eric Evans氏の意味するところのドメインオブジェクトと完全に一致するかは分からないが、wiki.c2.com では次のように説明されている。
A DomainModel is an object model of a problem domain. Elements of a domain model are DomainObject classes, and the relationships between them.
とりあえず、ドメインオブジェクトはドメインモデルを構成するオブジェクトという理解で問題なさそうだが、どうなのだろうか。
他人のドメインのオブジェクトはドメインオブジェクトなのか?
用語の実用性の観点から、そんなことはないんじゃないかと思うけれど、かとじゅんさんの言うように、言語の標準ライブラリや外部のライブラリが提供する、汎用的なDateやPointのようなオブジェクトも、他人のドメインのドメインオブジェクトだから、ドメインオブジェクトと呼ぶのだろうか?
DateやPointは汎用的なドメインに属するので、ドメインオブジェクトと呼べるというのがかとじゅんさんの論のようだ。では、汎用的なドメインとは何を指すのか? どうやら、汎用サブドメイン(Generic Subdomains) のことらしい。
「汎用的なドメイン」というのは、Evans本でいう「汎用サブドメイン」のこと?どういう場面で登場するかとか、注意すべき点はどこかというのは書いてありますね。それほど難しい事は言っていないと思います。
— Kazunori Otani (@katzchang) May 19, 2022
そうですね。汎用サブドメインですね。
— かとじゅん (@j5ik2o) May 19, 2022
DateやPointが汎用サブドメインに属する事があるから、それらをドメインオブジェクトと呼ぶことに不自然ではないことを判断するには、汎用サブドメインとは何かかが分かれば良さそうだ。では、DDD本の汎用サブドメインの説明を参照してみよう。
設計中のプロジェクトにとって動機となっていない、高凝集のサブドメインを識別すること。そうしたサブドメインから汎用的なモデルを括り出して、別のモジュールに入れること。その中に特化した要素を一切残してはならない。
いったん分離したら、その部分に対して継続される開発の優先順位をコアドメインより下げ、その作業にコアとなる開発者を割り当てるのを避けること(そこから得られるドメインについての知識はほとんどないからだ)。このような汎用サブドメインに対しては、既製品による解決策や公表されているモデルの採用も検討すること。
やはりDDD本は難解だが、プロジェクトの主たる動機とならないサブドメインから、さらに汎用的なモデルを括りだして集めたものが汎用サブドメインのようだ。つまり、ソフトウェアがプロジェクトの動機するドメインの更に中心的な存在であるコアドメインにたいして、それ以外のサブドメインに属するオブジェクトもドメインオブジェクトと呼ぶのであれば、汎用サブドメインに属するオブジェクトもドメインオブジェクトと呼ぶことができる。また、DDD本では金銭や通貨が汎用サブドメインのオブジェクトとして扱われている。これらのオブジェクトがあるドメインに属するドメインオブジェクトと説明されるのは十分に理解できる。ここから、次のような説明が成り立つのではないだろうか。
ドメインモデルを構成するすべてのオブジェクトはドメインオブジェクトである
この説明であれば他人のドメインに言及する必要はない。もちろん明確な定義が無いものに対して、これが真のドメインオブジェクトの解釈だと言い張る気はない。しかし、自分のスコープ外の他人のドメインに属するオブジェクトも、ドメインオブジェクトと呼ぶのであれば、オブジェクトとドメインオブジェクトの境界は無くなり、あるオブジェクトをドメインオブジェクトと呼ぶ意味は消失してしまう。
全部がドメイン固有オブジェクトなら、すべてがドメイン固有オブジェクトではなくなるような気がする。優先順位全部最優先だと優先度がすべてフラットになるのと同じように概念が消滅する。
— 渋川よしき (@shibu_jp) May 19, 2022
ドメインオブジェクトという用語の意味が消失しない説明をするためには、私のドメインオブジェクトだけが、ドメインオブジェクトでなければならない。他人のドメインに属するからDateやPointもドメインオブジェクトという説明にはやはり難があるのではないだろうか。一方で、ドメインモデルを構成するすべてのオブジェクトはドメインオブジェクトであるとするならば、DateやPointのような汎用的なオブジェクトも、ドメインによっては(私の)ドメインオブジェクトとすることができる。
バリューオブジェクトはドメインの不変条件への責任をもつのか?
不変条件(invariant)はドメインオブジェクトと違い、DDD本なかでも用語解説の中できちんと次のように説明されている。
不変条件(invariant) 何らかの設計要素についての表明で、常に真でなければならないもの。ただし、メソッドの実行中やまだコミットされていないデータベーストランザクション中といった特殊な過渡的状況を除く。
それでも定義としてはなんともふわりとしている。あるいはこれがDDDの味わいなのだろうか?この霞のようでゆるくふわりとした味わいがわかるようになれば、DDDマスターになれるのかもしれない。
それはともかく、DDDがPofEAAからバリューオブジェクトなどの用語を借用しているように、不変条件もおそらくはBertrand Meyer氏の契約プログラミングからの借用だろう。DDD本では不変条件の他にも、契約プログラミングの用語の事前条件や事後条件なども登場している。Bertrand Meyer氏の著作のオブジェクト指向入門 第2版 原則・コンセプトでは不変条件の定義を確認してみると不変条件ではなく、クラス不変表明(class invariant)として、第11章 契約による設計:信頼性の高いソフトウェアを構築する の 11.8 クラス不変表明 の冒頭で次のように説明されている。
事前条件と事後条件はここのルーチンの特性を記述する。このほかに、すべてのルーチンで維持されなければならない、クラスインスタンスに共通する全体的な特性を表す必要がある。そのような特性は、クラス不変表明(class invariant)といい、クラスの深い意味的な特性をとらえ、整合性を強いることで、クラスを特徴づける。
また、巻末のオブジェクト技術用語集では次のように説明されている。
クラス不変表明 [Class invariant] クラスの個々のインスタンスが生成された直後に満たされていて、かつ各公開(エクスポートされた)ルーチンの実行直後にも成立し続けていなければならない表明のこと。このことから、クラス不変表明は各インスタンスが外部から観察可能なときには、いつでも成り立っていることがわかる。
ついでにDDD本の説明にも登場しているので、表明という用語の解説も引いておこう。
表明 [Assertion] ソフトウェアの構成要素(特にルーチンとループ)を記述するための厳密な条件式。契約を表現するために用いられる。事前条件、事後条件、クラス不変表明、ループ不変表明が代表である。
なんだか難しいが、Wikipediaにもあるように、クラス不変表明とはクラスのインスタンスのコンストラクタやパブリックなメソッドが呼び出された後に、常に満たしてなければいけない状態に対する条件のことだ。
オブジェクト指向入門のEiffelや、DDD本の文脈で登場するJavaのような、クラスとオブジェクトを基礎概念として、クラスのインスタンスとしてオブジェクトを生成するオブジェクト指向言語では、バリューオブジェクトはクラスのインスタンスとして扱われるので、かとじゅんさんの解釈の中で登場する不変条件(invariant)も、このクラス不変表明(class invariant)のことだと仮定してもよいだろう。
DDD本ではパフォーマンスなどの制約がある場合を除いて、バリューオブジェクトを不変にしなければならないと記述されている。不変条件はクラスのインスタンスの状態への制約なので、不変なオブジェクトであれば、そのオブジェクトの振る舞いは、状態に対する条件を常に守ることになる。つまり、バリューオブジェクトはその構築以外ではプログラマがクラス不変表明を気にかける必要はない。
インスタンスの構築でクラス不変表明への責任を負うのは、必ずしもコンストラクタではない。Factoryパターンのようにファクトリが不変表明への責任を負う場合がある。不変表明をどこで保証すべきかについてはDDD本の 第6章 ドメインオブジェクトのライフサイクル ファクトリ(FACTORIES) でも言及されている。
ファクトリは、生成するオブジェクトや集約に関する不変条件が、すべて満たされていることを保証する責務を負っている。それでも、あるオブジェクトに適用されるルールを、そのオブジェクトの外部に移動させるなら、その前によく考えなければならない。ファクトリは、不変条件の検証を生成物に委譲することができ、それが最善であることも多いのである。
少なくとも、DDD本においてもバリューオブジェクトの不変表明を負うのが常にコンストラクタであるとは限らないのがわかる。したがって、バリューオブジェクトが常にドメインに対するクラス不変表明への責任を負うと言うのは難しいだろう。
バリューオブジェクトとDTOに包含関係はあるのか?
PofEAAのバリューオブジェクトがDTO(Data Transfer Object)を含むかどうかについて、過去にコミュニティで議論があったとかとじゅんさんの記事にはあるが、これはPofEAAでも言及されている。
15.2 データ変換オブジェクトの冒頭では次のように
この解決策は、呼び出しの全データを保持できるデータ変換オブジェクトを作成することである。それには、接続全体を直列化する必要がある。サーバ側では、データをDTOとドメインオブジェクト間で転送するのにアセンブラを使う。Sunコミュニティの多くの人は、このパターンについて「バリューオブジェクト」という用語を使っている。私はこの用語を少し異なる意味で使っているので、508ページの解説を参照することを勧める。
15.2.3 参考文献では次のように記されている
[Alur et al.]は、バリューオブジェクトの名でパターンを検討しているが、これは前述したデータ変換オブジェクトと同様である。私のバリューオブジェクトはまったく異なるパターンだ。同じ名前が異なる意味に使われているわけだが、ほとんどほとんどの人は私が使う意味でもバリューオブジェクトを使っている。言えることとしては、私がデータ変換オブジェクトを使っているのは、J2EEコミュニティ内においてだけであり、普通は一般的な用法に従ってきたということである。
Martin Fowler氏が、DTOのことを私のバリューオブジェクトはまったく異なるパターンと言っているので、DTOとバリューオブジェクトの間に包含関係を考える必要はなさそうだ。
DDDのバリューオブジェクトとは何であるのか?
バリューオブジェクトはドメインオブジェクトの一種かどうかは、それらは直交する概念なので、包含関係は存在しない。Martin Fowler氏のwikiにもあるように、たしかにドメインオブジェクトはエンティティ(参照オブジェクト)や、バリューオブジェクトや、サービスなどに分類できるが、それはバリューオブジェクトがドメインオブジェクトの一種であることを必ずしも意味しない。牛丼も並盛・大盛・特盛に分類はできるが、並盛は牛丼の一種だとはならない。豚丼や親子丼にだって並盛がある。
In his excellent book Domain Driven Design, Eric Evans creates a classification of the different kinds of domain objects that you're likely to run into.
・Entity: Objects that have a distinct identity that runs through time and different representations. You also hear these called "reference objects".
・Value Object: Objects that matter only as the combination of their attributes. Two value objects with the same values for all their attributes are considered equal. I also describe value objects in P of EAA.
・Service: A standalone operation within the context of your domain. A Service Object collects one or more services into an object. Typically you will have only one instance of each service object type within your execution context.
DDD本も見てみると、バリューオブジェクトは次のように説明されている。
あるオブジェクトが、ドメインにおける記述的な側面を表現し、概念的な同一性を持たない場合、そういうオブジェクトは、バリューオブジェクトと呼ばれる。バリューオブジェクトがインスタンス化される際に表現しようとするのは、何であるかだけが問題となり、誰であるか、あるいはどれであるかは問われないような設計の要素である。
いまいちよく分からない部分もあるが、PofEAAの中で説明されているバリューオブジェクトと同じように、同一性を持たないオブジェクトをバリューオブジェクトと呼んでいることはわかる。一方で、ドメインにおける記述的な側面を表現するとは何を意味するのか苦慮するところだ。僕にはよくわからなかったので教えてもらえるとうれしい。
また、バリューオブジェクトの不変性については次のように説明している。
値の実装を可変にするなら、それを共有してはならない。共有してもしなくても、可能ならば値オブジェクトを不変なものとして設計すること。
DDD本を読んでもいまいち分からないこともあるが、同一性を持たないオブジェクトを可能ならば不変にするというDDD本の説明は概ねPofEAAの説明と一致する。
IDに基づいた等価性を確保していない、 MoneyやDate Rangeなどのシンプルな小型オブジェクト。
ほとんどの言語はバリューオブジェクトの特定の機能を持っていない。バリューオブジェクトが機能するためには、バリューオブジェクトを不変、つまり作成時のフィールドがそのまま変更できないようにすることが有効である。理由は、別名割り当てのバグを防止するためである。
やはりバリューオブジェクトはPofEAAからの借用語なようなので、少なくともPofEAAの説明を満たすものがDDDにおけるバリューオブジェクトといっても差し支えはないだろう。このことは、あるバリューオブジェクトがドメインオブジェクトであることと矛盾しない。そして、バリューオブジェクトが、必ずしも(私の)ドメインオブジェクトだとは限らない。
まとめ
DDD本は20年も前の本だ。出版当時のソフトウェア開発の問題を解決して、その当時のコミュニティに受け入れられたかもしれないが、今となってはその内容に古臭さがあるのは仕方がないのだろう。この本がもっと用語の定義がしっかりしていればとも思うけれど、それではバリューオブジェクトの牛丼談義はなかっただろう。
まあ幸い牛丼談義は楽しい。用語の曖昧さもDDDの味わいだと思うのが良いだろう。そして、珠玉のアンサーソングを期待している。
ヘッダ画像はWikimedia CommonsからCC0 1.0に基づいて利用しています。
この記事が気に入ったらサポートをしてみませんか?