目次を表示する

EvansとVernonで学ぶDDD

第2章 ユビキタス言語 ── 言葉を統一することの意味

第2章 ユビキタス言語 ── 言葉を統一することの意味

ユビキタス言語とは何か

Eric Evansは『Domain-Driven Design』(2003年)の中で、ユビキタス言語(Ubiquitous Language)を「開発者とドメインエキスパートが共有する共通言語」と定義した。この言語はコード・テスト・ドキュメント・会話のすべてで一貫して使われる。どれか一つだけで使われる言語はユビキタス言語ではない。

重要なのは、ユビキタス言語が単なる「用語集」ではないという点だ。用語集は語彙の定義を固定するが、ユビキタス言語はモデルと言語が双方向に洗練し合うダイナミズムを持つ。ドメインエキスパートと開発者が対話を重ねる中で、言語はモデルの理解を深め、深まったモデルの理解がまた言語を精錬する。このサイクルが止まった瞬間、ユビキタス言語は死語になる。


なぜユビキタス言語が必要か

採用管理システムの開発現場を例に取る。

ビジネス担当者は「応募者が審査を通過したら連絡する」と言う。開発者がコードを書くと、userテーブルを参照してreviewフラグを確認するロジックが生まれる。一見問題ないように見えるが、ここには三重の乖離がある。

  • ビジネスが「応募者」と呼ぶ概念を、エンジニアはuserテーブルで管理している
  • ビジネスが「審査」と呼ぶ概念を、コードではreviewという変数名で表現している
  • 「通過」という状態変化の表現がコードに存在しない

この乖離が積み重なると何が起きるか。仕様変更の打ち合わせでビジネス担当者が「審査ステータスを追加したい」と言っても、開発者は「reviewフラグを増やすということですか」と翻訳作業が必要になる。翻訳ミスがバグになる。「応募者がキャンセルした場合」という要件が、userの削除なのかreviewフラグのリセットなのか判断できなくなる。

言語の乖離は、コミュニケーションコスト・バグ・仕様誤解の三つを同時に生産する。


EvansとVernonの共通認識

Vaughn Vernonは『Implementing Domain-Driven Design』(2013年)でユビキタス言語についてEvansと同じ立場を取り、さらに実践的な観点を加えた。Vernonは「ユビキタス言語なくしてDDDなし」という姿勢を一貫して示している。ドメインモデルをどれほど精緻に設計しても、そのモデルを表現する言語がドメインエキスパートに通じなければ、モデルは開発者の自己満足に終わるとVernonは指摘する。

両者の共通点は明確だ。ユビキタス言語はプロジェクト初期に一度決めて終わりではなく、ドメインへの理解が深まるにつれて継続的に改訂される。この改訂を厭わないことが、DDDの実践における前提条件だとEvansもVernonも述べている。


採用管理システムでのユビキタス言語の確立例

言語を統一する作業は、ビジネス用語・旧来のコード用語・統一後のユビキタス言語の三列で整理すると進めやすい。

ビジネス用語旧コード用語ユビキタス言語
応募者user候補者(Candidate)
求人票job / position求人(JobPosting)
審査review選考(Screening)
審査ステップreview_step / stage選考段階(ScreeningStage)
不合格rejected / failed不採用(Rejected)
担当者recruiter / hr_user採用担当者(Recruiter)

ユビキタス言語が確立したら、それをそのままコードに反映する。クラス名・メソッド名・変数名がドメインエキスパートとの会話で使う言葉と一致している状態が目標だ。

// 候補者エンティティ
class Candidate {
  constructor(
    readonly candidateId: CandidateId,
    readonly name: CandidateName,
    readonly contactInfo: ContactInfo
  ) {}
}

// 求人エンティティ
class JobPosting {
  constructor(
    readonly jobPostingId: JobPostingId,
    readonly title: JobTitle,
    readonly requiredSkills: Skill[]
  ) {}
}

// 選考プロセスエンティティ(集約ルート)
class ScreeningProcess {
  readonly id: ScreeningProcessId;
  readonly candidateId: CandidateId;  // IDのみ参照(候補者オブジェクトは持たない)
  readonly jobPostingId: JobPostingId;
  private currentStage: ScreeningStage;

  // 選考を次の段階に進める
  advance(to: ScreeningStage): void {
    this.currentStage = to;
  }

  // 選考で候補者を不採用にする
  reject(reason: RejectionReason): void {
    this.currentStage = ScreeningStage.Rejected;
  }
}

コードレビューの際、「このメソッドのadvanceって何を進めるの?」という質問は出ない。ドメインエキスパートに「選考を次の段階に進めるメソッドです」と説明すれば、そのままコードを指差して伝わる。これがユビキタス言語の効果だ。


言語の境界という問題

ユビキタス言語の確立が進むと、別の問題が浮上する。「候補者」という言葉の意味が、コンテキストによって微妙に異なるという問題だ。

求人管理コンテキストでは、候補者は「どの求人に応募してきたか」という観点で捉えられる。選考管理コンテキストでは、候補者は「選考のどの段階にいるか」という観点で捉えられる。通知コンテキストでは、候補者は「どのメールアドレスに何を送るか」という観点でしか存在しない。

同じ「候補者」という言葉が、コンテキストをまたぐと指す対象が変わる。これを一つのユビキタス言語で統一しようとすると、概念が肥大化して管理不能になる。

この問題の解決策が「境界づけられたコンテキスト(Bounded Context)」だ。各コンテキストの内側でユビキタス言語を確立し、コンテキスト間の言語の差異は明示的なマッピングで管理する。第3章ではこのメカニズムを詳しく扱う。


まとめ

ユビキタス言語の本質は、言葉の統一ではなくモデルと言語の継続的な共進化にある。コードに書かれた言葉がドメインエキスパートの言葉と一致することで、翻訳コストが消え、バグの混入経路が一つ塞がれる。採用管理システムであれば、userテーブルのreviewフラグを操作するコードではなく、CandidateScreeningProcessを操作するコードが、ビジネスの実態に近い。

ただし、ユビキタス言語はシステム全体で一つである必要はない。むしろ一つにしようとすること自体が問題を生む。この点については次章で論じる。


インフォグラフィック

サマリー

インフォグラフィック: ユビキタス言語が機能するサイクル

Before: 言語が乖離した状態

graph TD
    subgraph ビジネス側
        B1["ドメインエキスパート<br/>「応募者の審査を管理したい」"]
    end

    subgraph 開発側
        D1["開発者<br/>userテーブルのreviewフラグを更新"]
        D2["コード<br/>user.review_status = 'done'"]
    end

    B1 -->|"翻訳(誤解のリスク)"| D1
    D1 -->|"実装"| D2
    D2 -.->|"コードを読んでも<br/>ビジネス意図が不明"| B1

    style B1 fill:#ffd6d6
    style D1 fill:#ffd6d6
    style D2 fill:#ffd6d6

After: ユビキタス言語が確立された状態

graph TD
    subgraph ドメインエキスパート
        E1["「候補者の選考を<br/>次の段階に進める」"]
    end

    subgraph 開発者
        D1["「ScreeningProcess.advance()<br/>を呼び出す」"]
    end

    subgraph コード
        C1["screeningProcess.advance(<br/>  ScreeningStage.Interview<br/>)"]
    end

    E1 -->|"同じ言葉で対話"| D1
    D1 -->|"言葉をそのまま実装"| C1
    C1 -->|"コードがモデルを表現<br/>→ 言語がさらに洗練"| E1

    style E1 fill:#d6f5d6
    style D1 fill:#d6f5d6
    style C1 fill:#d6f5d6

ユビキタス言語の適用範囲

graph LR
    UL["ユビキタス言語<br/>(候補者・選考・求人)"]

    UL --> Conv["会話<br/>「候補者の選考を進める」"]
    UL --> Doc["ドキュメント<br/>選考フロー仕様書"]
    UL --> Test["テスト<br/>it('候補者を選考の次段階に進める')"]
    UL --> Code["コード<br/>ScreeningProcess.advance()"]

    style UL fill:#e8f4fd,stroke:#2196F3,stroke-width:2px