インラインコード
前置き
DDD集約(Aggregate)とコンポーネントの凝集原則(特にCCP: Common Closure Principle / 閉鎖性共通の原則)は、異なる粒度と目的を持っていますが、密接に関連しています。
この関係性を理解し、設計フェーズから運用フェーズにかけて一貫したフィードバックループを構築することが、成功するシステム設計の鍵となります。
以下に詳細なメカニズムと、具体的なワークフローを解説します。
第1部:理論とメカニズム
1. DDDの集約 (Aggregate) とは
目的:「トランザクション整合性の維持」
スコープ:マイクロ視点(論理的なデータ塊)
メカニズム
密接に関連するオブジェクトのグラフを一つのユニットとして扱い、外部からは「集約ルート(Aggregate Root)」を通してのみ操作を許可します。
これにより、集約内部の不変条件(ビジネスルール)が常に守られることを保証します。
原則
「1つのトランザクションで更新されるのは1つの集約のみ」が理想です(結果整合性を用いる場合を除く)。
2. コンポーネントの凝集原則 - CCP (Common Closure Principle) とは
目的:「保守性とデプロイ容易性の向上」
スコープ:マクロ視点(物理的なデプロイ単位)
jarファイル、npmパッケージ、マイクロサービスなど。
定義
同じ理由、同じタイミングで変更されるクラスは、同じコンポーネントに属すべきである。
これは単一責任の原則(SRP)をコンポーネントレベルに適用したものです。
メカニズム
関連する機能を持つモジュール群を物理的にまとめることで、変更の影響範囲を閉じ込め、デプロイの頻度やリスクを管理します。
3. 集約とCCPの関係メカニズム
この二つは「包含関係」と「因果関係」にあります。
①. 集約はCCPの最小単位である
適切に設計された集約は、内部のエンティティや値オブジェクトが「強い整合性」のために結びついています。
これは最強の「同じ理由(ビジネスルールの維持)で同じタイミング(トランザクション)で変更される」関係です。
したがって、一つの集約が複数のコンポーネントに分割されることはあってはなりません。
②. CCPは複数の集約を束ねる指針となる
コンポーネントは通常、複数の集約を含みます。
どの集約を同じコンポーネントに入れるかを決める際、CCPが指針となります。
「集約Aと集約Bはトランザクション境界は別だが、関連するビジネス要件(例:税制変更)によって常に同時に修正が入る」場合、これらは同じコンポーネント(CCP準拠)に配置する候補となります。
「集約Aと集約Bをモジュールとして境界定義し、それらを1つの同じサービスコンポーネント境界で束ねる」などがその代表例です。
まとめ
集約は「データの整合性」で境界を引き、CCPは「変更の理由とタイミング」でその集約たちを物理的な箱(コンポーネント)に詰める指針です。
第2部:実践フロー(設計フェーズ)
では、モジュールが増え、カオス化してきた段階で、どのように境界を見直すか。
実際にどんなログなどをモニタリングして意思決定していくのかを見てみましょう。
ステップ1:現状把握と変更ログの分析ツール
モジュールが増えてきた段階では、ソースコード管理システム(Gitなど)が真実を語ります。「どのファイルが一緒に変更されているか(Co-change)」を分析します。
使用ツールは、以下の通りです。
Git (コマンドライン)
git log --name-only --oneline や git log --follow [ファイルパス] を駆使して、コミット単位で同時に修正されたファイル群を特定します。
GUIツール
GitKraken, SourceTree, IntelliJ IDEA のGit履歴ビューなどは、視覚的に同時変更を追いやすいです。
専用分析ツール (高度)
CodeScene などのツールは、Gitリポジトリを解析し、「論理的結合度(Logical Coupling)」をヒートマップで可視化してくれます。
「このファイルとあのファイルは、依存関係定義はないのに70%の確率で同時にコミットされる」
といった隠れた結合を暴きます。
ステップ2:トランザクション範囲との比較
次に、「歴史的な変更事実」と「あるべきビジネスの整合性(トランザクション)」を比較します。
1. トランザクション範囲の特定(仮説)
・まだコードがない、あるいはコードが混乱している場合は、「イベントストーミング」や「ユースケース分析」を行います。
・「ユーザーが『注文確定』ボタンを押したとき、絶対に同時に成功または失敗しなければならないデータは何か?」を定義します。これが「集約の暫定的な範囲」です。
事例
注文と注文明細は同時でなければならない(一つの集約)。
しかし、在庫の引き当ては、注文とは別のトランザクション(結果整合性)でも良いかもしれない(別の集約)。
2. 比較と分析
・ケースA(理想)
Gitのログで「常に同時に変更されるファイル群」が、分析した「トランザクション範囲」と完全に一致している。
→ 設計は正しい。
・ケースB(集約が大きすぎる疑い)
一つの集約として設計したフォルダ内のファイル群が、Gitログを見るとバラバラのタイミングで変更されていることが多い。
→ 集約が肥大化しており、内部で異なる責務(変更理由)が混在している。分割を検討。
・ケースC(集約が小さすぎる、またはCCP違反)
別々の集約(または別々のコンポーネント)として定義したモジュールが、Gitログを見ると常に同時に変更されている。
→ トランザクション境界を見直して統合するか、少なくともCCPに基づいて同じコンポーネントに物理的に同居させるべき。
ステップ3:集約およびコンポーネントの範囲決定
上記の分析に基づき、仮説モデルを構築します。
1. 集約の決定
まず、強い整合性が必要な範囲で集約を定義します(トランザクション境界)。
2. コンポーネントの決定(CCPの適用)
定義した集約群を眺め、「同じビジネスルール変更の影響を受ける集約たち」を一つのコンポーネントにまとめます。
第3部:実践フロー(運用・検証フェーズ)
構築した仮説モデルが正しいか、運用しながら検証します。「この内部のクラス群やモジュール群はすべて同じタイミングで同じ理由で変更されている」ことをどう確認するか。
1. モニタリングすべきログとメトリクス
運用フェーズでは、静的なコード分析だけでなく、動的な振る舞いと開発プロセス全体を監視します。
A. CI/CDとバージョン管理のメトリクス(開発プロセスの視点)
コミットの凝集性
1つの機能追加/修正チケットに対して、複数のコンポーネントにまたがるコミットが行われていないか?
もし頻発するなら、CCP違反(コンポーネントの境界が変更の理由と一致していない)の兆候です。
コンポーネントのデプロイ頻度と同期性
コンポーネントAをデプロイするとき、常にコンポーネントBも同時にデプロイしないと互換性が壊れる、という状況になっていないか?
これは強い結合の証拠であり、境界設定ミスの可能性があります。
B. アプリケーションログとトレース(実行時の視点)
トランザクションログとエラー
一つのビジネス操作で、複数の集約にまたがる更新が頻繁に発生し、その一部だけ失敗してデータ不整合が起きるエラーログが出ていないか?
→ 集約の境界が小さすぎる(本来一つのトランザクションであるべき)。
分散トレーシング (Jaeger, Datadog, AWS X-Rayなど)
・あるリクエストがコンポーネント間をどのように流れるかを可視化します。
・「チャッティな(おしゃべりな)通信」を監視します。
コンポーネントAとBの間で、1回のビジネスロジック遂行のために同期的なAPIコールが往復で大量に発生している場合、それらは本来一つのコンポーネント(または集約)であるべきだった可能性があります。
2. 仮説の妥当性検証の判断基準
「境界設計は間違っていない」と判断するためのポジティブなシグナルと、ネガティブなシグナルです。
✅ 妥当性が高いと判断できる状態(成功のシグナル)
変更が閉じている
新しいビジネス要件(例:新しい割引ルールの追加)が発生した際、その変更が「一つの集約内」または「一つのコンポーネント内」の修正だけで完結し、他に波及しない。
デプロイが独立している
あるコンポーネントを修正してデプロイした際、他のコンポーネントが壊れることを恐れずにリリースできる。
ログが語る「同じ理由」
Gitのコミットメッセージを見返したとき、あるコンポーネント内のファイル群の変更理由が「〇〇機能の仕様変更対応」で統一されており、他の無関係な理由が混ざっていない。
❌ 境界を見直すべき状態(失敗のシグナル)
ショットガン手術
一つの仕様変更のために、多数のコンポーネントや集約を少しずつ修正して回る必要がある。(CCP違反の典型)。
予期せぬ副作用
集約Aの一部を変更したら、全く関係ないと思っていた集約Bのロジックが壊れた。(集約内部の凝集度が低い、または隠れた結合がある)。
巨大な泥団子化
特定の「コア」コンポーネントがあらゆる変更のたびに修正され、肥大化し続けている。(責務が集まりすぎている、CCPの誤用)。
事例:ECサイトの「注文」と「配送」
仮説構築(設計フェーズ)
初期分析で、「注文(Order)」と「配送(Shipping)」は別の集約として定義した。
「注文が確定してから、配送指示が出る」という時差があるため、結果整合性で良いと判断したとします。
運用フェーズでの検証
シナリオ1(成功)
「配送業者のAPIが変更された」という要件が発生。
Gitログ/CIメトリクスを確認
「配送コンポーネント」のみが修正され、デプロイされた。
「注文コンポーネント」には一切触れる必要がなかった。
判断
境界は正しい。
CCPが守られている(配送に関する変更理由が配送コンポーネントに閉じている)。
シナリオ2(失敗 - 見直しが必要)
「注文時に、配送先の地域に応じて特定の商品の販売を制限したい」という要件が発生。
開発の実態
「注文」集約でバリデーションロジックを追加しようとしたが、「配送」集約が持つ詳細な地域データや配送可能マスタが必要だと判明。
結果として、「注文」と「配送」の両方のコードを同時に修正し、両方のコンポーネントを同時にデプロイする必要が生じた(ショットガン手術)。
判断
境界設計が現状のビジネス要件に合っていない。
「配送先住所に基づく検証」は注文の整合性の一部であるべきだった可能性が高い。
アクション
「配送先情報(Value Object)」を「注文」集約の中に取り込むか、あるいはドメインサービスを導入して両者の調整役を再定義するなどのリファクタリングを検討する。