ドメインモデリングとは?業務の言葉とルールをコードにする方法をPythonで解説|進め方と失敗例

「申し込み」「定員」「キャンセル待ち」「繰り上げ」と書かれた付箋から矢印が伸び、それらを属性とメソッドに持つSeminarクラスの図

ドメインモデリングとは、ソフトウェアで扱う業務(ドメイン)の言葉とルールを整理し、それをそのままコードの構造に写し取る設計の作業です。「申し込み」「定員」「キャンセル待ち」のような業務の言葉と、「定員を超えたらキャンセル待ちに回す」のような業務のルールを、クラスやメソッドとして表現します。

この記事では、ドメインモデリングとは何か、なぜ必要なのかを、ルールを持たないコードとドメインモデルのコードをPythonで動かしてくらべながら解説し、業務の言葉集めからコードに落とすまでの進め方を紹介します。ドメイン駆動設計(DDD)の用語の全体像は「DDD(ドメイン駆動設計)の基本概念」で解説しているので、この記事では実際にモデルを作る手順に重点を置きます。

ドメインモデリングとは

ドメインとは、ソフトウェアが対象にする業務や問題の領域のことです。セミナーの予約システムならセミナーの申し込みと運営、ネットショップなら商品の注文と配送が、ドメインにあたります。

ドメインモデルは、そのドメインを理解するために、大事な概念とルールだけを取り出して整理したものです。地図が土地の情報のうち目的に必要なものだけを描いているように、ドメインモデルも業務のすべてを写すのではなく、ソフトウェアで扱う問題を解くのに必要な部分だけを表します。

そしてドメインモデリングは、業務に詳しい人(ドメインエキスパート)と開発者が話し合いながら、このモデルを作り、コードに反映し、改善し続ける作業全体を指します。2003年にエリック・エヴァンスが著書『Domain-Driven Design』(邦訳『エリック・エヴァンスのドメイン駆動設計』)で体系化したドメイン駆動設計(DDD)の中心にある考え方です。

言葉 意味
ドメイン ソフトウェアが対象にする業務や問題の領域
ドメインモデル ドメインの大事な概念とルールを整理したもの。図やコードで表す
ドメインモデリング ドメインモデルを作り、コードに反映し、改善し続ける作業
ドメイン駆動設計(DDD) ドメインモデルを中心にソフトウェアを設計する手法の全体

なぜドメインモデリングが必要なのか:Pythonでくらべる

ドメインモデリングの効き目は、コードで見るのが一番わかりやすいです。題材は、テックジムの無料講座のようなセミナーの申し込みにします。業務のルールは次の3つとします。

  • 定員を超えて参加を確定させない。満席なら「キャンセル待ち」に入れる
  • 同じ人が二重に申し込むことはできない
  • 参加者がキャンセルしたら、キャンセル待ちの先頭の人を繰り上げる

ルールを持たないコード(ドメインモデル貧血症)

まずは、セミナーのクラスをデータの入れ物として作り、申し込みの処理は画面ごとの関数に書いた例です。

# ルールを持たない「データの入れ物」だけのクラス(ドメインモデル貧血症)
class Seminar:
    def __init__(self, title, capacity):
        self.title = title
        self.capacity = capacity
        self.participants = []   # 申し込んだ人のメールアドレス

# 申し込みの処理を書く場所がバラバラになりがち
def apply_from_web(seminar, email):
    if len(seminar.participants) < seminar.capacity:   # Web画面ではチェックしている
        seminar.participants.append(email)

def apply_from_admin(seminar, email):
    seminar.participants.append(email)                 # 管理画面ではチェックを忘れた

s = Seminar("Python入門講座", capacity=2)
apply_from_web(s, "a@example.com")
apply_from_web(s, "b@example.com")
apply_from_admin(s, "c@example.com")   # 定員オーバーなのに入ってしまう
apply_from_admin(s, "a@example.com")   # 同じ人が二重に申し込めてしまう
print(f"定員 {s.capacity}人 / 参加者 {len(s.participants)}人: {s.participants}")

実行結果は次のとおりです(Python 3.9で実行)。

定員 2人 / 参加者 4人: ['a@example.com', 'b@example.com', 'c@example.com', 'a@example.com']

定員2人のセミナーに4人が入り、同じ人が二重に申し込めてしまいました。Web画面の処理では定員を確かめていましたが、管理画面の処理ではそのチェックを忘れているためです。

このように、クラスがデータを持つだけで業務のルールを持たず、ルールがあちこちの処理に散らばっている状態を、マーチン・ファウラーは「ドメインモデル貧血症(Anemic Domain Model)」と呼びました。ルールを書く場所が増えるほど、どこかで書き忘れたり、少しずつ違うルールになったりします。

ルールを持ったドメインモデル

次に、業務のルールをセミナーのクラスの中に集めた例です。「メールアドレス」「定員」も、ただの文字列や数値ではなく、ルールを持った小さなクラス(値オブジェクト)にしています。

from dataclasses import dataclass, field

# ---- 値オブジェクト:値そのものに意味とルールを持たせる ----
@dataclass(frozen=True)
class Email:
    value: str

    def __post_init__(self):
        if "@" not in self.value:
            raise ValueError(f"メールアドレスの形式ではありません: {self.value}")

@dataclass(frozen=True)
class Capacity:
    value: int

    def __post_init__(self):
        if not 1 <= self.value <= 100:
            raise ValueError("定員は1〜100人で設定します")

class SeminarError(Exception):
    """業務のルールに反する操作"""

# ---- 集約:ルールを守る責任を1か所に集める ----
@dataclass
class Seminar:
    title: str
    capacity: Capacity
    participants: list = field(default_factory=list)   # 参加が確定した人
    waitlist: list = field(default_factory=list)       # キャンセル待ちの人
    events: list = field(default_factory=list)         # 起きた出来事(ドメインイベント)

    def apply(self, email: Email):
        """申し込む。満席ならキャンセル待ちに入る"""
        if email in self.participants or email in self.waitlist:
            raise SeminarError(f"{email.value} はすでに申し込んでいます")
        if len(self.participants) < self.capacity.value:
            self.participants.append(email)
            self.events.append(f"参加が確定した: {email.value}")
        else:
            self.waitlist.append(email)
            self.events.append(f"キャンセル待ちに入った: {email.value}")

    def cancel(self, email: Email):
        """キャンセルする。空いた席はキャンセル待ちの先頭の人に回す"""
        if email not in self.participants:
            raise SeminarError(f"{email.value} は参加者ではありません")
        self.participants.remove(email)
        self.events.append(f"キャンセルされた: {email.value}")
        if self.waitlist:
            moved = self.waitlist.pop(0)
            self.participants.append(moved)
            self.events.append(f"繰り上げで参加が確定した: {moved.value}")

このクラスを使って、申し込み・二重申し込み・キャンセル・ありえない値の作成を試します。

from model import Seminar, Capacity, Email, SeminarError

s = Seminar("Python入門講座", Capacity(2))
for addr in ["a@example.com", "b@example.com", "c@example.com"]:
    s.apply(Email(addr))

for addr in ["a@example.com"]:          # 二重の申し込み
    try:
        s.apply(Email(addr))
    except SeminarError as e:
        print("エラー:", e)

s.cancel(Email("a@example.com"))         # 1人キャンセル → キャンセル待ちが繰り上がる

for bad in [lambda: Email("abc"), lambda: Capacity(0)]:   # ありえない値は作れない
    try:
        bad()
    except ValueError as e:
        print("エラー:", e)

print("参加者:", [p.value for p in s.participants])
print("キャンセル待ち:", [w.value for w in s.waitlist])
print("出来事:")
for ev in s.events:
    print("  -", ev)
エラー: a@example.com はすでに申し込んでいます
エラー: メールアドレスの形式ではありません: abc
エラー: 定員は1〜100人で設定します
参加者: ['b@example.com', 'c@example.com']
キャンセル待ち: []
出来事:
  - 参加が確定した: a@example.com
  - 参加が確定した: b@example.com
  - キャンセル待ちに入った: c@example.com
  - キャンセルされた: a@example.com
  - 繰り上げで参加が確定した: c@example.com

今度は、3人目はキャンセル待ちに入り、二重の申し込みはエラーになりました。1人がキャンセルすると、キャンセル待ちの人が自動で繰り上がっています。「@」のないメールアドレスや、定員0人のセミナーは、そもそも作ることができません。

ポイントは、業務のルールがSeminarクラスのapplyとcancelの中にしかないことです。Web画面からでも管理画面からでも、申し込みは必ずapplyを通るので、ルールの書き忘れが起きません。また、メソッドの名前が「申し込む(apply)」「キャンセルする(cancel)」と業務の言葉そのものなので、コードを読めば業務のルールがわかります。

なお、Pythonではparticipantsのリストに外から直接追加することもできてしまいます。実際の開発では、名前を_participantsにして「外から触らない」ことを示すなど、チームで約束を決めておきます。値オブジェクトで使った@dataclass(frozen=True)の使い方は「Python __eq__とは?」などのPythonの記事も参考にしてください。

ドメインモデルを作る部品

上のコードには、ドメインモデリングでよく使う部品がいくつも入っています。

部品 説明 上の例では
値オブジェクト 値そのものに意味とルールを持たせた、変更できない小さなオブジェクト。中身が同じなら同じものとして扱う Email、Capacity
エンティティ 中身が変わっても同じものとして追いかけたい対象。IDなどで区別する セミナー(申し込みが増えても同じセミナー)
集約 ルールを守るためにひとまとめに扱うオブジェクトのかたまり。変更は必ず入り口(集約ルート)を通す Seminarと、参加者・キャンセル待ちのリスト
ドメインイベント 業務の中で起きた、意味のある出来事 「参加が確定した」「繰り上げで参加が確定した」
不変条件 いつでも必ず成り立っていなければならないルール 参加者は定員以下、同じ人は1回だけ

このほか、集約を保存・取り出すためのリポジトリや、特定の集約に属さない処理をまとめるドメインサービスなどの部品もあります。それぞれの詳しい説明は「DDD(ドメイン駆動設計)の基本概念」をご覧ください。

ドメインモデリングの進め方

ドメインモデルは、机の上で一人で考えて作るものではありません。業務に詳しい人と話し合いながら、次のような流れで作り、改善していきます。

1. 業務の言葉を集めて、用語集を作る

最初にやるのは、業務で使われている言葉を集めることです。同じものを人によって「申込者」「参加者」「受講者」と呼んでいたり、同じ「予約」という言葉が人によって違う意味だったりすることがよくあります。言葉のずれは、そのまま仕様のずれになります。

そこで、業務の人と開発者が同じ意味で使う言葉を決めて、用語集にまとめます。DDDではこれをユビキタス言語と呼びます。決めた言葉は、会話にも、資料にも、コードのクラス名やメソッド名にも同じように使います。

用語 意味 コードでの名前
申し込み セミナーへの参加を希望すること。参加の確定とは別 apply
参加者 申し込みのうち、参加が確定した人。定員まで participants
キャンセル待ち 満席のときに申し込んだ人。申し込んだ順に並ぶ waitlist
繰り上げ キャンセルで空いた席に、キャンセル待ちの先頭の人を参加者にすること cancelの中の処理

この表を作る過程で、「申し込んだ人は全員参加者なのか?」「キャンセル待ちは何人まで?」のように、仕様のあいまいな点が見つかります。モデリングの価値の多くは、この「あいまいな点の発見」にあります。

2. 起きる出来事を時系列に並べる(イベントストーミング)

業務の流れをつかむには、イベントストーミングという方法が便利です。2013年ごろにアルベルト・ブランドリーニが考案したワークショップの手法で、業務で起きる出来事を「〜した」という過去形で付箋に書き、時間の順に壁に並べていきます。

セミナーの例なら、「セミナーが公開された」「申し込みを受け付けた」「参加が確定した」「キャンセル待ちに入った」「キャンセルされた」「繰り上げで参加が確定した」「リマインドメールを送った」「セミナーが開催された」のように並びます。並べると、「キャンセルはいつまでできる?」「開催日の前日にキャンセルされたら繰り上げる?」といった、まだ決まっていない点が出来事のすき間に見えてきます。上のコードのeventsは、この出来事をそのままコードにしたものです。

3. ルール(不変条件)を見つける

出来事と出来事の間にある「〜なら〜する」「〜してはいけない」を書き出します。「定員を超えて参加を確定させない」「同じ人は二重に申し込めない」のように、いつでも守られていなければならないルールが、集約で守るべき不変条件になります。

4. モデルを図にする

見つけた概念とルールを、簡単な図にします。きれいなUMLを描く必要はありません。チームで同じものを見ながら話せることが大事です。テキストで図を描けるMermaidなら、次のように書けます。

classDiagram
    class Seminar {
        title
        capacity : Capacity
        participants : Email[]
        waitlist : Email[]
        apply(email)
        cancel(email)
    }
    class Capacity { value }
    class Email { value }
    Seminar --> Capacity
    Seminar --> Email

5. コードに落とし、テストで確かめる

図にしたモデルを、上の例のようにコードにします。このとき、ユビキタス言語の言葉をクラス名・メソッド名にそのまま使うこと、ルールは集約の中に書くことを意識します。そして、「定員を超えたらキャンセル待ちに入る」「キャンセルしたら繰り上がる」といったルールごとにテストを書いておくと、後で変更しても安心です。

6. 学んだことでモデルを直し続ける

モデルは一度作って終わりではありません。運用してみると、「当日のキャンセルは繰り上げない」「同じ会社から3人までしか申し込めない」のような新しいルールが必ず出てきます。そのたびに用語集とモデルとコードを一緒に直します。エヴァンスは、この継続的な改善で業務への理解が深まり、モデルがよくなっていくことを重視しました。

ドメインモデリングでよくある失敗

  • データベースの表から考えてしまう:テーブル設計から入ると、データの入れ物としてのクラスになり、ルールがあちこちに散らばる「ドメインモデル貧血症」になりやすい。先に業務の言葉とルールから考える
  • 開発者だけで決めてしまう:業務の人の言葉を聞かずに作ったモデルは、業務の実態とずれる。用語集とイベントストーミングは、業務の人と一緒に行う
  • 全部を1つのモデルにしようとする:「顧客」という言葉でも、営業と経理では見ている情報が違う。規模が大きくなったら、言葉の意味が変わる境目でモデルを分ける(DDDでは「境界づけられたコンテキスト」と呼ぶ)
  • 小さなシステムにも重い設計を持ち込む:ルールがほとんどない、データを出し入れするだけのシステムなら、凝ったモデルは必要ない。ドメインモデリングが効くのは、業務のルールが複雑なところ

AIと開発する時代のドメインモデリング

Claude CodeのようなAIコーディングツールにコードを書かせるときも、ドメインモデリングは役に立ちます。AIは、業務の言葉とルールを知らなければ、一般的なもっともらしいコードを書きます。たとえば、上の「ルールを持たないコード」のような実装を出してくることも少なくありません。

そこで、ユビキタス言語の用語集と不変条件の一覧を、プロジェクトの説明ファイル(Claude CodeならCLAUDE.md)に書いておくと、AIもその言葉とルールに沿ってコードを書くようになります。用語集は、人間のチームのためだけでなく、AIと共通の言葉で話すためのものにもなります。CLAUDE.mdの書き方は「Claude Code の各種 MDファイル完全活用ガイド」で解説しています。

ドメインモデリングを学ぶための本

書名 特徴
エリック・エヴァンス『エリック・エヴァンスのドメイン駆動設計』 DDDの原典(原著は2003年)。ドメインモデリングの考え方を最初に体系化した本。分厚く難しいので、入門書の後に読むのがおすすめ
ヴォーン・ヴァーノン『実践ドメイン駆動設計』 原典の考え方を、実際のシステムにどう当てはめるかを解説した本(原著は2013年)
スコット・ウラシン『Domain Modeling Made Functional』 関数型プログラミング(F#)でドメインをモデリングする本(原著は2018年)。「ありえない状態を型で表せないようにする」考え方がわかりやすい

最初は、この記事のような小さな例を自分の身近な業務(アルバイト先のシフト、部活の備品の貸し出しなど)に置き換えて、用語集を作り、ルールをクラスに書いてみるのが一番の練習になります。

よくある質問

ドメインモデリングとは簡単に言うと何ですか?

ソフトウェアで扱う業務の言葉とルールを整理して、それをそのままコードの構造にする設計の作業です。業務に詳しい人と開発者が話し合いながら進めます。

ドメイン駆動設計(DDD)との違いは何ですか?

ドメインモデリングは、業務のモデルを作り、コードに反映する作業そのものです。DDDは、ドメインモデリングを中心に、用語集(ユビキタス言語)、モデルの分け方(境界づけられたコンテキスト)、コードの部品(エンティティ・値オブジェクト・集約など)までを含めた設計手法の全体を指します。

データベース設計(ER図)とは違うのですか?

ER図はデータをどう保存するかを表します。ドメインモデルは、データだけでなく業務のルールや振る舞い(申し込む、キャンセルする)も表します。保存の都合ではなく、業務の都合で考えるのが違いです。

小さなプログラムでもドメインモデリングは必要ですか?

業務のルールがほとんどないプログラムなら、凝ったモデルは必要ありません。ただ、「業務の言葉をそのまま名前に使う」「ルールは1か所に書く」という考え方は、小さなプログラムでも役に立ちます。

まとめ

ドメインモデリングは、業務の言葉とルールを整理し、それをそのままコードの構造にする設計の作業です。Pythonの例で見たように、ルールをデータと一緒に1つのクラスに集めると、書き忘れによる不具合を防げ、コードを読めば業務のルールがわかるようになります。進め方は、業務の言葉を集めて用語集を作り、出来事を並べ、ルールを見つけ、図とコードにして、学ぶたびに直し続けることです。

Pythonでクラスを使った設計を基礎から身につけたい人は、テックジムの無料のプログラミング講座から始めてみてください。AIと一緒に設計・開発を進める方法を学びたい人は、AI駆動開発/生成AIエンジニアコースもご覧ください。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら