DDDでドメインを設計するときの流れ

― 実践的な設計手順 ―

ドメイン駆動設計(DDD)を学び始めると、多くの人が次の疑問を持ちます。

  • ドメイン設計は何から始めればいいのか
  • 実際の開発ではどのような手順で設計するのか

DDDの本では概念の説明が多く、「実際の設計手順」がはっきりしないことがあります。

この記事では、実務で使える ドメイン設計の具体的な手順 を整理して紹介します。


ドメイン設計の全体の流れ

DDDでドメインを設計する場合、次の順序で進めると整理しやすくなります。

  1. ユースケースを書き出す
  2. ドメイン用語を整理する(ユビキタス言語)
  3. エンティティ候補を出す
  4. 不変条件を書く
  5. 状態遷移を整理する
  6. 集約(Aggregate)を決める
  7. エンティティの責務を設計する
  8. 必要ならドメインサービスを作る
  9. リポジトリを定義する
  10. 最後にUIを設計する

重要なのは UIから設計を始めないこと です。

DDDでは、基本的には、

ユースケース → ドメイン → UI

という順序で設計します。

実際には、一度でバシッと決められるわけではないため、ドメインを設計しているときに、ユースケースに戻るなど、各プロセス間を何度も行ったり来たりします。


この記事の前提

この記事では、口座振替管理アプリの開発を想定して、設計の流れを説明してきます。

振替元と振替先をユーザーが紐づけるメモアプリを想像してください。


1. ユースケースを書き出す

最初に、システムでユーザーが行う操作を書き出します。

ポイントは UIではなく行動を書くこと です。

【例】

  • 口座を登録する
  • 振替関係を作る
  • 振替関係を停止する
  • 振替予定を確認する

ここでは 動詞を使って定義する ことが重要です。

この一覧が、アプリケーションのユースケースになります。


2. ドメイン用語を整理する(ユビキタス言語)

次に、ドメインで使う重要な言葉を整理します。

【例】

  • 口座
  • サービス
  • 振替関係
  • 振替予定
  • 支払日

ここでやることは次の2つです。

  • 同じ意味の言葉を統一する
  • UIの言葉をそのまま使わない

例えば次のような状態は避けます。

【NG 例】

  • 支払い元
  • 銀行
  • 口座

これらは意味が同じである場合、例えば 口座 に統一します。

DDDでは、このようにチーム全体で共通の言葉を使うことを ユビキタス言語 と呼びます。


3. エンティティ候補を出す

次に、システムの中心になる概念を探します。

【例】

  • Account
  • Service
  • TransferRelation
  • TransferSchedule

この段階では、まだ厳密に決める必要はありません。 重要なのは ドメインの主要な概念を見つけること です。


4. 不変条件を書く

DDDで最も重要なのが 不変条件(Invariant) です。

不変条件とは

どんな操作をしても絶対に破ってはいけないルール

です。

【例】

  • 振替関係は必ず1つの口座を持つ
  • 振替関係は必ず1つのサービスを持つ
  • 振替日は1〜31の範囲
  • 「口座とサービス」が同じ組み合わせの振替関係は、重複できない

このように 箇条書きで書く のがポイントです。

実際の設計では、この不変条件がシステム仕様の大部分を決めます。


5. 状態遷移を整理する

状態を持つエンティティについては 状態遷移 を整理します。

【例:振替関係】

作成
↓
有効
↓
停止
↓
削除

さらに重要なのは

どの操作で状態が変わるのか

です。

【例】

  • create → 有効
  • stop → 停止
  • resume → 有効
  • delete → 削除

この整理をしておくことで、 不正な状態遷移を防ぐことができます。


6. 集約(Aggregate)を決める

DDDでは、不変条件を守る単位を 集約(Aggregate) と呼びます。

特に、複数のエンティティをまたぐ不変条件は、それらを束ねる集約で定義します。

【例】

TransferRelation
├ AccountId
├ ServiceId
└ paymentDay

このエンティティは次のルールを守ります。

「口座 + サービス」が同じ組み合わせの振替関係は 1 つだけ (重複不可)

このルールを守る境界が 集約 です。


7. エンティティの責務を設計する

次に、エンティティのドメインメソッドを設計します。

【例】

class TransferRelation {

    stop()

    resume()

    changePaymentDay()
}

ここで重要なのは setterを作らないこと です。

【NG】

setPaymentDay()

【OK】

changePaymentDay()

では、なぜ setter は避けるべきなのでしょうか。

setterが問題になる理由

setPaymentDay() のような setter は、単に値を代入するだけのメソッドです。

transferRelation.setPaymentDay(50)

このコードは 技術的には成立してしまいます

しかし、ドメインのルールには次のものがあります。

  • 振替日は1〜31の範囲

つまり、setPaymentDay() を許すと

  • 不正な値が設定できてしまう
  • 不変条件が破られる

という問題が起こります。

ドメインメソッドは「操作」を表す

DDDでは、エンティティのメソッドは 単なる値変更ではなく、ドメインの操作を表す べきです。

changePaymentDay(newDay)

という名前にすると

  • 支払日を変更するという操作
  • その操作にはルールがある

という意味を持たせることができます。

例えば次のように実装できます。

fun changePaymentDay(newDay: Int) {
    require(newDay in 1..31)
    paymentDay = newDay
}

このように 不変条件のチェックをエンティティの中に閉じ込める ことができます。

重要な考え方

DDDでは、エンティティは単なるデータ構造ではありません。

エンティティはビジネスルールを守る存在です。

そのため

  • setXxx() のような単純な setter
  • 外部から自由に状態を変更できる設計

は避けます。

代わりに

  • changePaymentDay()
  • stop()
  • resume()

のように ドメインの操作を表すメソッド を設計します。

これによって

  • 不変条件が守られる
  • ドメインモデルが壊れにくくなる
  • コードから業務の意味が読み取れる

というメリットが生まれます。


8. ドメインサービスを定義する

エンティティに入らない処理は ドメインサービス にします。

無理に作成する必要はなく、必要な場合のみ作成します。

【例】

TransferExecutionService

【責務】

  • 今日振替すべきものを取得する
  • 振替を実行する

エンティティが持つべきでない処理をここに配置します。


9. リポジトリを定義する

永続化のためのインターフェースです。

【例】

  • TransferRelationRepository
  • AccountRepository
  • ServiceRepository

リポジトリは ドメイン層にインターフェースだけ定義 します。


10. 最後にUIを作る

ここまで終わって、初めてUIを設計します。

多くのアプリは次の順序で設計されます。

UI
↓
ViewModel
↓
Repository
↓
DB

しかし、DDDでは次の順序になります。

ユースケース
↓
ドメイン
↓
UI

この順序にすると

  • UI変更の影響を受けない
  • ビジネスルールが守られる
  • 設計が崩れにくい

というメリットがあります。


まとめ

DDDでドメイン設計をする際は、次の手順で進めると整理しやすくなります。

  1. ユースケースを書く
  2. ドメイン用語を整理する
  3. エンティティ候補を出す
  4. 不変条件を書く
  5. 状態遷移を整理する
  6. 集約を決める
  7. エンティティの責務を設計する
  8. ドメインサービスを定義する
  9. リポジトリを定義する
  10. 最後にUIを設計する

DDDの設計で最も重要な問いは次のものです。

「このルールはどこで守るのか?」

この問いを繰り返すことで、ドメインモデルは徐々に洗練されていきます。


この流れで設計する際のテンプレート

この流れで設計する際のテンプレートを ドメイン設計テンプレート に用意しました。