アーキテクチャ●●●●○

ドメイン駆動設計

ドメインくどうせっけい

業務領域の言葉とモデルを中心に据える設計手法。複雑なドメインをコードに写し取る。

概要

ドメイン駆動設計(DDD: Domain-Driven Design)は、ソフトウェアが対象とする業務領域 — ドメイン — の理解とモデル化を設計の中心に据える手法です。2003年の Eric Evans の著書で体系化されました。ここでいうモデルとは、業務の複雑さを整理するための抽象で、「保険の契約とは何か」「与信とはどういう判断か」といった業務の概念・ルール・関係を、そのままの語彙でコードに写し取ることを目指します。

DDD が力を発揮するのは、技術的な難しさよりも業務そのものの複雑さが支配的なシステムです。金融、物流、医療、ECの在庫と価格計算のように、ルールと例外が幾重にも折り重なる領域では、データベースの形や画面の都合から設計を始めると業務知識がコードのあちこちに霧散します。DDD はその逆で、まず業務の専門家と開発者が共通の理解を作り、その理解の構造をコードの構造に一致させようとします。

なぜ生まれたか

2000年代初頭までに、オブジェクト指向やMVCのようなパターンで「コードをどう整理するか」の道具は揃いつつありました。しかし大規模な業務システムの失敗は減りませんでした。原因の多くは技術ではなく翻訳にあります。業務の専門家は「解約」と言い、仕様書は「契約終了処理」と書き、コードは deleteUser と実装する — 同じ概念が場所ごとに違う名前と微妙に違う意味を持ち、翻訳のたびに認識のずれが混入していたのです。しかも業務ルールはサービス層・画面・SQLに散らばり、「この業務の本当のルールはどこに書いてあるのか」に誰も答えられなくなります。

Evans の主張は、ソフトウェア開発の核心は「ドメインについての学習」であり、学習の成果であるモデルを話し言葉とコードの両方で同じ形に保つべきだ、というものでした。モデルとコードが一致していれば、業務の専門家との会話がそのまま設計レビューになり、業務の変化はコードの変更点を直接指し示します。設計手法であると同時に、開発者とドメイン専門家の協働のやり方を定めた方法論でもある点が、DDD を単なるパターン集と分けています。

詳細

ユビキタス言語 — 全員が同じ言葉を話す

DDD の土台は「ユビキタス言語」です。ドメイン専門家と開発者が議論を通じて用語の意味を厳密に合意し、その言葉を会話・ドキュメント・クラス名・メソッド名のすべてで一貫して使います。「注文の確定」と呼ぶと決めたなら、コードにも confirmOrder があるべきで、finalizePurchase という別名の同居は許しません。言葉のゆらぎを潰す作業は一見地味ですが、その過程で「確定とキャンセルは同時に起こり得るのか」といった業務の曖昧さが炙り出されることこそが本当の収穫です。

境界づけられたコンテキスト — モデルには適用範囲がある

DDD で最も重要な洞察は、「全社で通用する統一モデルは作れないし、作るべきでもない」というものです。同じ「商品」という言葉でも、販売の文脈では価格やキャンペーンが本質で、配送の文脈では重量やサイズが本質です。これを1つの巨大な商品クラスに統合すると、あらゆる部門の都合を背負った密結合の塊が生まれます。そこで DDD は、1つのモデルとユビキタス言語が通用する範囲を「境界づけられたコンテキスト」として明示的に区切り、コンテキストごとに別のモデルを持つことを推奨します。境界を跨ぐ連携は、変換層(腐敗防止層)を挟んで相手のモデルが自分の内部に漏れ込まないように守ります。これはまさに結合度と凝集度の議論をシステム分割のスケールで実践するもので、コンテキスト間の関係を地図にしたものはコンテキストマップと呼ばれます。

販売コンテキスト商品価格 / 割引 / キャンペーン在庫の引き当て「売れるもの」としての商品独自のユビキタス言語を持つ配送コンテキスト商品重量 / サイズ / 梱包区分配送先と追跡番号「運ぶもの」としての商品独自のユビキタス言語を持つ変換境界を跨ぐときは変換層を挟み、相手のモデルが内部へ漏れ込むのを防ぐ
境界づけられたコンテキスト — 同じ「商品」でもコンテキストごとに別のモデルを持つ

戦術的設計 — モデルをコードに刻む部品

コンテキストの内側でモデルを実装するための部品群が「戦術的設計」です。エンティティは、属性が変わっても同一性が識別子で追跡される対象です(注文番号で追う注文など)。値オブジェクトは、同一性を持たず値そのものが本体である不変の対象です(金額、住所、期間など)。金額を生の数値ではなく Money 型にすれば、「円とドルをうっかり足す」類のバグを型で締め出せます。集約は、一緒に整合性を保つべきエンティティと値オブジェクトのまとまりで、外部からの変更は集約ルート(例: 注文)経由に限定し、トランザクションの境界も原則1集約に揃えます。このほか、集約の取得と保存を抽象化するリポジトリ、生成の複雑さを引き受けるファクトリ、「注文が確定した」のような出来事を表すドメインイベントなどの部品があります。リポジトリの実装には ORM がよく使われ、部品の組み立てには依存性注入が定番です。

注意したいのは、DDD の本体はあくまでユビキタス言語と境界設計(戦略的設計)であることです。戦術的パターンだけを形式的に導入し、業務との対話を欠いたまま「エンティティとリポジトリのあるいつもの CRUD」を作ってしまうのは、軽量DDDと呼ばれる典型的な失敗です。逆に、単純な管理画面のような領域に集約や値オブジェクトのフル装備を持ち込むのも過剰投資です。コストをかける価値があるのは事業の核心となる複雑なドメイン(コアドメイン)であり、周辺領域は素朴な実装や既製品で済ませる、という濃淡の付け方自体が DDD の教えに含まれています。

マイクロサービスと現代的な展開

DDD は2010年代に再脚光を浴びました。理由はマイクロサービスです。モノリスをどう分割するかという問いに対し、「境界づけられたコンテキストをサービス境界にする」が最有力の答えになったためです。業務的な意味の境界を無視して技術レイヤや画面単位で切ると、サービス間の呼び出しが密になり分散システムの複雑さだけを背負い込むことになります。コンテキスト間の連携にはドメインイベントをメッセージキューで流す非同期連携が好相性で、状態変化そのものをイベントの列として記録するイベントソーシングや、更新系と参照系のモデルを分離するCQRSも、DDD のモデリングと組み合わせて語られることの多い技法です。「システムの境界は業務の境界に従う」という DDD の核心は、アーキテクチャの流行が変わっても参照され続けています。