🐙現場で役立つシステム設計の原則から学んだDDD実践知識

subaru · ·

増田亨「現場で役立つシステム設計の原則」とVaughn Vernon「実践ドメイン駆動設計」を読んで、特に重要だと感じた概念とルールをまとめた。


DDDはなぜ必要か

ソフトウェアが失敗する原因の大半は、技術的な問題ではなく業務の理解不足にある。

  • 仕様書通りに作ったのに、実際の業務で使われない
  • ビジネスロジックがどこにあるかわからない(サービス?DB?フロント?)
  • 新しい要件を追加するたびにバグが出る

これらは、コードが現実のビジネスを正確に反映できていないから起きる。

DDDが解決しようとするのは、ソフトウェアの中心に「業務(ドメイン)」を置くことだ。

従来の発想:
  要件定義 → DB設計 → API設計 → 実装

DDDの発想:
  ドメインを理解する → ドメインモデルを設計する → それをコードに写す

ただしDDDはすべてのプロジェクトに向かない。業務ロジックが複雑な場合にのみ真価が出る。単純なCRUDシステムでは過剰設計になる。


ユビキタス言語

ドメインエキスパート(業務担当者)と開発者が共同で作り上げ、会話・ドキュメント・コードのあらゆる場面で一貫して使い続ける共通言語のこと。

ドメインエキスパートは「契約」「顧客」「請求」という言葉で話す。開発者は「Contractテーブル」「Userレコード」「Invoiceオブジェクト」という言葉で話す。この翻訳コストが要件の誤解とモデルの乖離を引き起こす。

ユビキタス言語を確立することで、ドメインモデルが業務の言葉で直接表現され、翻訳コストがゼロになる。

実践として:

  • ドメインエキスパートとのミーティングで出てきた用語をそのままクラス名・メソッド名に使う
  • コードレビューで「このクラス名は業務側が使う言葉と一致しているか?」を確認する
  • 用語に揺れが生じたら即座にチームで議論して合意する

注意点として、ユビキタス言語は境界づけられたコンテキストの中でのみ一貫性が保証される


境界づけられたコンテキスト

特定のドメインモデルが適用される明示的な境界。境界の内部では:

  • ユビキタス言語が一貫して使われる
  • 「顧客」「注文」「商品」などの概念が一つの意味しか持たない

境界の外では同じ言葉が異なる意味を持つことがある。例えば「顧客」は販売コンテキストでは「購入者」、サポートコンテキストでは「チケットを上げる人」を意味しうる。

単一の巨大なドメインモデルを全システムで共有しようとすると、モデルが膨大な概念を抱え込み、チーム間の変更が互いに影響し合いリリースが困難になる。Bounded Contextを明示的に設けることで、各チームが自律的にモデルを進化させられる

マイクロサービスの境界を決める際の目安は「1コンテキスト≒1サービス」。


ドメインオブジェクトへの業務ロジック集約

業務に関するロジック(計算・判断・状態遷移)を、データを保持するドメインオブジェクト自身に持たせる設計方針。

サービス層やコントローラーに業務ロジックが漏れ出す「ドメインモデル貧血症(Anemic Domain Model)」を避ける。

  • ロジックがドメインオブジェクトに集まると、関連する変更が1箇所に収まる
  • サービス層がデータの受け渡しに徹することで、ユースケース記述が読みやすくなる
  • 業務の変化に対してどこを修正すればよいかが明確になる

「この計算はどこに書くべきか」と迷ったときは、計算の対象となるデータを持つオブジェクトに書く。


ルール: 設計はドメインモデルから始める

新機能・新システムの設計開始時は、DBスキーマやAPIの設計より先に業務の言葉・ルール・概念を整理する。「どのテーブルを作るか」ではなく「業務ではどんな概念が動いているか」から問いを始める。

DBやAPIから設計を始めると:

  • 技術的な都合がドメインモデルを歪める(正規化のためにドメインの概念が分断されるなど)
  • 業務の変化がDBスキーマ変更に直結し、変更コストが高くなる
  • コードが「データの取得・保存」中心になり、業務ロジックの置き場所がなくなる

例外:純粋なCRUDシステム(業務ロジックがほぼない)ではDBスキーマ中心でも問題ない。


ルール: 業務ロジックはドメインオブジェクトに書く

業務上の計算・判断・バリデーションは、そのデータを保有するドメインオブジェクトのメソッドとして実装する。サービス層(ApplicationService / UseCase)は「誰を呼ぶか」のオーケストレーションのみとし、業務判断を持たない。

サービス層に業務ロジックを書くと:

  • 同じ判断が複数のサービスに重複する
  • ドメインオブジェクトがデータの入れ物になり、変更箇所の特定が困難になる
  • ユニットテストがサービス層の結合テストになってしまう

例外:複数の集約をまたぐ処理(例:注文と在庫の整合性)は、特定のドメインオブジェクトに属さないためサービス層に置く。ただしその場合も各集約のドメインオブジェクトへできる限り委譲する。


出典

  • 現場で役立つシステム設計の原則(増田亨)第1・3・4章
  • 実践ドメイン駆動設計(Vaughn Vernon)第1・2章
  • ドメイン駆動設計(Eric Evans)序文