たなかこういちの開発ノート

システム開発に携わる筆者が、あれこれアウトプットするブログ

私的実践DDD、その1

ようやく「実践ドメイン駆動設計」を読み終わりました。
 
 
実際にありそうな開発プロジェクトのストーリーに沿うかたちで話が進められます。コードExampleが極めて具体的に示されて、どうすべきかが非常に分かりやすい参考書だと思いました。コードのExampleはJava、C#がメインでしたが、これのScala版(関数型言語版)があったら、これまたすごい参考書になりそうな予感がしました。
 
DDD(Domain Driven Design、ドメイン駆動設計)は、エンタープライズ・システムのアーキテクチャーに関するパターン集です。DDDが言うのは「パターン」ですので、DDDの用いる用語でそれを認識しておらずとも、DDDが言及するパターンを少なくとも部分的には適用している開発の現場は実は数多くあるはずなのです。私もその辺整理、確認したくて同書を読みました。いろいろ触発されるところもあったので、「私的実践DDD」としていくつかトピックをあげていきたいと思います。
 
〜・〜
 
集約はエンティティの<<has>>関係と<<uses>>関係の見極めでほぼ機械的に決定できる
 
まず、DDDのいう「集約」はUMLクラス図でいう集約と80%同一です。違いは、次に説明する「機械的な方法」で決定された集約は、UMLでは形式を満たしさえすれば全て集約と言えますが、DDDではその内の“ほんとうに意味のある塊”だけがDDD的に集約と名乗れるものとなります。機械的方法で決定した集約の内の20%は、DDD的に不適当である可能性があります。
 
要は「80%は使える」ということです。もっと実践的に言えば、DDD的な分析に不慣れな(※ただし、OOには慣れている必要あり)メンバーでも機械的に80%の精度のアウトプットを出せる、ということです。エキスパートは残り20%をカバーすればよい。
 
※「80%」、「20%」の数字は適当です。根拠のあるものではないです。
 
「機械的な決定方法」とは次のようなものです。
 
(1) エンティティの関連をUMLクラス図で描きます。
(2) 関連を<<has>>と<<uses>>の二種類だけで分類します。徹底してこの二種類だけで描いてください。エンティティのライフサイクルを考えれば直ぐに決定できます。おおよそエンティティAが削除されたらBも連動して削除されるだろうというとき"A has B"です。Aが削除されたからといってBは削除されない、というとき"A uses B"です。"A has B"の関係は、通常、逆から見ると"B uses A"となります。一方、"A uses B"の関係を逆からみても"B has A"とならずに、逆からみても"B uses A"となるような関係があります。なお、<<is>>、すなわち継承関係はここでは無視してください。派生末端のサブクラスだけを取り扱い対象とし基底クラスは無視してください。
(3) ここでどちらかがどちらかに対して<<has>>関係にあるエンティティは同一の集約に含めるべきで、双方向に<<uses>>であるような関連が集約の分割ライン、となります。
(4) クラス図の全体として<<has>>関係は循環しないように注意します。循環しているときは何か共通の“親”となるようなエンティティを見落としています。<<uses>>関係は循環して構いません。
 
なお、ライフサイクルの連動性は“削除”で見るのがミソです。“作成”でみると、<<has>>関係でもタイミングが異なっている場合が多く、見極めが難しいです。
 
※実は個人的にはこの<<has>>、<<uses>>分析だけで100%やれると思っているのですが、世の中100%はあり得ないので、公式には「80%はやれる」と言っておくこととします。「80%」という数字はつまり「100%、とはさすがに言わないが・・・」という程度の意味です。
 
唯一つの集約(とその集約に関わるドメインサービス)を一つの「Microservice」として実装すべし
 
(DDDの話では無くなってますが、、、)マイクロサービス(Microservice)は下記記事がおおよそその原典となっています。
 
Martin Fowler、"Microservices":http://martinfowler.com/articles/microservices.html
 
よく「マイクロサービスといっても、その単位をどう切ったらいいかわからない」という問いを見受けます。マイクロサービスの技術的特徴とDDDの集約の技術的特徴には、極めて似ている点があります。具体的にそれは何かというと、、
 
「ACIDトランザクションスコープの最小単位である」
 
、、ということです。あるACIDトランザクションがあったとき、そのスコープをほんとうにさらに分割できるのか否かの見極め自体は難しいかもしれません。しかし、なにせ一つのACIDな単位を見定めることができたならば、それはDDDの集約であるし、マイクロサービスの単位としてもちょうど良いものと言えます。
 
マイクロサービス間のデータ連携は、Eventually Consistency(結果整合)なBASEトランザクションで構成するのが妥当とされています。(※その方が、サービスの運用の独立性をより確保できるため。)逆に言えば、どうしてもACIDであるべきデータ連携は、マイクロサービス分割すべきでない、ということです。
 
DDDとしては集約やリポジトリを必ず分散構成にせよとは言っていませんが、適切な集約を見極めるには分散構成が可能な程度に疎結合な線引きをいずれにせよ見出す必要があります。
 
※BASEトランザクションやEventually Consistencyについては、次の記事も参照してみてください。
 
 
ACIDトランザクションしか念頭に無いと集約の分割は難しい・・・
 
前節、前々節と矛盾することを言うかもしれませんが、DDDを実践する上で現時点で最も難しいのが、「集約」を分割することとACIDトランザクションのスコープ設定が両立しなさそうに思えることだと思います。おそらくこれは事実難しくて、結論から言うとBASEトランザクションなりEventually Consistencyを上手くやってくれる基盤やフレームワークが無いと、ほんとうにDDDが言うようにきれいには集約を実装し得無い、と思います。
 
BASEトランザクションやEventually Consistencyをサポートする基盤無しで、つまりACIDトランザクションだけでやろうとすると、結局アプリケーションレベルで、ドメインが担う処理のACID性がどこにあるべきか知っていなければならず、アンチ・パターンとされている「トランザクション・スクリプト」へ陥っていくことへの“歯止め”を効かせるのが難しくなると感じています。・・・ちょっと主張が弱いですね。言い直すと、BASEトランザクションで切ることができるならば、概念的な分割ラインと実装的な分割ラインを完全に1対1対応させられるので迷いが出にくいのに対し、BASEトランザクションで切れない場合は、概念的な分割ラインは概念的にのみ保守しなければならないので、分割境界が侵食されやすいだろう、ということです。・・・まだ曖昧だな。。さらに言い直すと、、、集約を正しく認識できているならば、問題は無いです。集約を認識できていない時、今まさに集約の分割をどうしようか検討している時、トランザクションの方式としてACIDトランザクションしか念頭に無いと、そのトランザクションに関わっているエンティティが同一集約に属すべきか、異なる集約に分けるべきか、見極めが難しくなるだろう、という話です。BASEトランザクションで分割することを理解していれば、集約も正しく認識できるでしょう、ということです。
 
※「実践ドメイン駆動設計」でも、トランザクションスコープの扱いについては少々ブレが見受けられます。例えば、10章2節には「集約はトランザクション整合性の境界と同義である」との記述がある一方で、12章4節には「ドメインモデルの永続化に関するトランザクションをとりまとめるには、アプリケーションレイヤを使うのが一般的だ」との記述があります。
 
※「BASEトランザクションなりEventually Consistencyを上手くやってくれる基盤やフレームワークが無いと・・・」と思っていたところ、次のようなTweetが流れてきました。
 
 
◆以上
(※次回へ続きます。)