「ドメイン駆動設計」感想(1) - なぜファットモデルになるのか


エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)


正月にこれを第2部まで読んだ感想を書こうと思って、何ともう2月後半になってしまった。
いろいろ考えさせられたことを忘れてしまう前に感想文を書きます。


(DBばっかりいじっててコードを書かない)俺みたいなのから見たオブジェクト指向設計の特徴に、「実体(エンティティ)の存在は認めても、関係(リレーションシップ)の存在をなかなか認めない」、つまり関係を極力クラスとして立てない、というのがある。
例えば、部門と社員の関係を「所属クラス」として独立させるより、オブジェクト間の関連=参照を握る/握られるで表現する(部門 has_many 社員)ことを好むのだ。
エリック・エヴァンスの考えるDDD*1もまた、この特徴を受け継いでいるように見える。


DDDのモデル

DDDがモデルから関係を排除している例を見てみましょう。
前掲書P-70に、銀行の口座振替のシーケンス図が載っている。
話に関係ある部分だけ切り出すと、こんな感じになる:

口座への入金・出金ロジック(=credit, debitメソッド)が、口座オブジェクト自身に取り付けられていることがわかる。


このシーケンス図では、2つの口座の残高を操作するメソッドは見えているが、「何月何日に、a口座からb口座に、いくら送金した」というトランザクションデータを生成するタイミングは描かれていない。よくわからないが、P-71に「紙幅の関係で、本来あるべき元帳オブジェクトや ... 金銭取引オブジェクトなどを省略した」という意味のことが書いてあるので、どこかで生成するつもりなのだろう。たぶん左端の「資金振替サービス」のtransferToメソッドの中で、ログみたいな処理の副産物として、トランザクションを生成するのではないか。
いずれにしても、このモデルにおいてはトランザクションは単なるデータであって、能動的なオブジェクトとして処理を駆動していない。

アナリシスパターンのモデル

入出金ロジックを口座に取り付ける以外にも、オブジェクト指向設計的にアリなモデルは存在する。
アナリシスパターン―再利用可能なオブジェクトモデル (Object Technology Series) P-113には、ファウラーお勧めのモデルとして、トランザクションが口座(同書の表現では「勘定」となっている)の明細(同じく「エントリ」)を生成する例が描かれている。
話に関係ある部分だけ切り出すと、こうだ:



DDD本とは操作の方向が逆になっていることがわかる。


ファウラーのモデルでは、口座自身ではなく、口座と口座の関係であるトランザクションが振替処理を主管する。
DDDのモデルなら「資金振替サービス」のレイヤにあたる何かに生成されたトランザクションオブジェクトが、2つの口座の明細を生成するのだ。
このトランザクションオブジェクトは、たぶん{ 日付時刻, 借方口座, 借方金額, 貸方口座, 貸方金額 }といったデータ項目を持つ永続化対象であり、DDDの分類で言えばサービスではなくエンティティに相当する。
ファウラーのモデルでは、口座オブジェクトは入金・出金ロジックを持たず、いわば「裏口から」勝手に明細を追加される受身の存在だ*2


ファットモデルの原因

DDD本とアナリシスパターン、それぞれのモデルを採用した場合の結果はどうなるだろうか。
集約(Aggregates)を重視するDDDの立場から見れば、口座明細は口座の後ろに隠れているべきものであって、口座を経由せずに誰かが勝手に口座明細を作成するのは問題あり、ということになるのかもしれない。DDDは、集約を迂回して必要なオブジェクトだけをつかむ書き方には冷淡だ。前掲書P-148にこんな風に書いてある。

クライアントコードがデータベースを直接使用していると、開発者は、集約のようなモデルの機能や、オブジェクトのカプセル化さえも迂回し、必要なデータを直接取り出して操作したくなってしまう。...開発者は、欲しいオブジェクトを何でも直接つかんでしまおうという気になる。

この点についてはDDDのモデルの方が優れているのかもしれない。が、よくわからない。正直言って集約というアイデアは、実践上どこまで貫徹できるのか怪しいと思う。


ファウラーのモデルが優れているのは、口座オブジェクトがより純粋・シンプルなところだ。
逆にDDDの口座オブジェクトはよりファットだ。振替処理というただ1つのユースケースを実装するために、口座には入金・出金ロジックと、たぶん口座明細を生成するロジックが追加されている。
ファウラーのモデルでは、これら全てが口座間の関係であるトランザクションオブジェクトに吸収され、口座自身の「純度」が保たれている*3


「口座が入出金ロジックを持っているのはまったく自然な設計であって、何の問題もない」と考える人もいるかもしれない。俺も自然な設計だとは思う。
しかし、特定のユースケースを処理するためのメソッドやメンバ変数の追加を無条件に許すと、口座のようなシステムの中核クラスはどんどん膨れ上がり、いわゆるファットモデルが誕生する。
そしてDDDを実践する場合、「自然な設計*4」を優先して、エンティティ間の関係を積極的にクラス化しなかったら、システムの規模拡大に伴ってファットモデルが発生するのは避けられないと思う。

*1:DDDにもいろいろあるんだろうけど、以下単にDDDと表記する

*2:この図で口座の残高属性が更新されていないのは、「残高はエントリを集計して出す導出項目」という解釈だからだろう。結果、振替処理に口座はまったく参加しない

*3:私はT字形ER手法の考え方に従い、エンティティが他のエンティティとの関係を内部に取り込んでいることを「純度の低下」と考えている。例えば社員クラスに「所属部門」という名前の、部門クラスのメンバ変数があったら、それは所属という関係を内部に取り込んでヨゴレていると見なす。純度が下がって何が悪いかというと、クラスが際限なく肥大するのが良くない

*4:最近はメンタルモデルとか言うのだろうか