DDDは結局、なにを実現したいのか

thumbnail

DDDというと何が頭に浮かぶでしょうか?
Entityだの、Layered Architectureだの、依存関係逆転の法則だの、調べるとそういった「コードの書き方」が多く目に入ります。

しかしそれは、DDDを実現するための手段でしかありません。
手段が目的化すると、ドメインモデル貧血症のように記述量が増えたわりに結局何も実現できてなかったということになりがちです。
ですので、この記事ではそもそもDDDは何を実現したいのかという点からまとめていきます!

DDDは何をしたいか

DDDはそもそも、何を実現したいんでしょうか。
エヴァンス本の最初の「まえがき」には、こう書いてあります。

私はこの10年間、...複雑なシステムを開発してきた。
...成功するプロジェクトに共通する特徴は、豊かなドメインモデルがあったということだ。

簡単にいうと「良いドメインモデルがあったときにプロジェクトが成功したぜ!」というわけですね。

DDDは良いドメインモデルを介してプロジェクトを成功させることこそが目的であり、そのために

  1. ドメイン知識をドメインモデルに落とし込む
  2. ドメインモデルを実装に落とし込む

の2点を適切に行う方法を説明している手法なのです。

DDDというと実装方法ばかり注目されることが多いですが、このドメインモデルこそがDDDで最も重要なものなのです。
逆に言えばドメインモデルを無視して実装方法だけ真似しても、それは少なくともエリックのやりたいことではありません。

ドメインモデルって?

ところで、このドメインモデルってなんなのでしょうか。
ドメインモデルは、エヴァンス本ではこう書かれています。

ドメインモデルとは特定の図ではなく、図が伝えようとしている考え方である。
これはドメインエキスパートの頭の中にある単なる知識ではなく、その知識が厳密に構成され、選び抜かれて抽象化されたものなのだ。

いきなりこう言われても、わけわからないですよね。
DDDが理解しづらい原因は、新しい言葉がどんどん出てくることにあると思います。
ですのでこの記事では、はじめはドメインモデル=UML図として説明していきます。

ただ実際にはUML図はドメインモデルではないと明確に否定されていますので、この時点では話を進めるための仮の理解としておいてください。
両者の違いについては補足に記載しています。

従来の設計との違い

さて、ドメインモデルがUML図だと、DDDは単に要件に合わせてUML図を作ってそれを実装するだけとなってしまいます。
これだとUML図までとはいかなくても、脳内なりなんなりで誰もがやっている作業でないでしょうか。

エヴァンスは、多くの開発において、この作業が一方通行で行われることが問題だとしています。

具体的には次のようなものが、その例といえるでしょう。

  1. SEが顧客にヒアリングを行いUML図を作成する。
  2. 作成されたUML図を元に、プログラマーは開発を行う。

この一連の作業に、逆方向のフィードバックはありません。
ビジネスを適切に表現できてないUML図であっても顧客は気づけませんし、開発上問題があるUML図であってもそれは反映されません。

したがってまず重要なのは、ドメインモデルに対するフィードバックをエンジニアだけでなくチーム一体となって行うことなわけです。

ドメインモデルのフィードバックを回すために

では、どうすればフィードバックを適切に回すことができるでしょうか。
そのためには、チーム全員がドメインモデルに対して向き合う必要があります
エンジニアだけでなく、デザイナーや企画、顧客など、全員が意見を言い合えないといけないのです。

ユビキタス言語

チーム全員がドメインモデルに対して向き合うとは、職種をまたいで会話をするということです。
そのためには、使う言語を揃えないといけません。
これをDDDではユビキタス言語と呼びます
要は「みんなで同じ言葉使おうぜ!」ってことですね。

チーム内で言語が変わるケース

コミュニケーションが分断されていると、使われる言葉も変わりがちです。
たとえば企画者は顧客、エンジニアはユーザ、営業はお客様など、同じ概念に対しての普段使ってる言葉はバラバラになりがちです。
この程度ならまだしも、複雑なドメイン固有の用語が統一できてないと相手が何言ってるかわかったもんじゃありません。

さらに悪いケースだと、フロントエンドとバックエンドで使われてる単語が違ったり、人によって違ったりするケースもあります。
こうなると「実装した人に聞かないとわからない」「他人の書いた箇所をいじるのが怖い」って状況につながっていき、もはや保守も困難になっていきます。

言語が異なると起こる問題

またチーム間で言語が異なると、通訳の存在が必要となります
そしてその役割は一般的にマネージャーが担うこととなりがちです。

するとマネージャーだけが他職種と関わるMTGに参加し、その後に職種内で共有されるような組織運営につながっていきます。
つまりユビキタス言語がないことで、取れる組織の形も制限されていきます。

アジャイル開発など、チームの自己組織化を進めていくためには、チーム全員で会話できるようにする必要があります。
つまりチームの自己組織化を進めていくためにも、ユビキタス言語を作ることは重要となってくるのです。

日本固有の問題

さて、「じゃあユビキタス言語を作ろう!」と思っても、日本の場合そう簡単にはいきません。

というのも、日本語でクラス名やテーブル名をつけづらいので、どうしてもエンジニアだけ別の言葉が生まれてしまうからです。

こうなると他職種はエンジニアの会話がわかりませんし、エンジニア側も「仕様書にある顧客管理番号がコード上のどれにあたるのかわからない」なんてことになってしまいます。
そのため開発寄りのことを話す際は、「よくわからないからエンジニアだけでお願い」となってしまいがちなのです。

エヴァンスは日本に来た際に、この問題について「日本語でコード書けば良いんじゃない?」と言っていたとのことです。
一長一短ありますが、正しくDDDを実践するのであればそれが一番良いのかもしれません。

少なくとも、コード上にコメントを残したり、英単語とユビキタス言語の対応表を用意するなど、チーム全員が認識を一致できる仕組みは必要となります。

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

ドメインの領域が大きいと、どうしても1つの単語に対する意味が複数生まれてくることがあります。

たとえばAirbnbでは、宿泊者かホストかで同じ「予約」と単語に対する意味合いは大きく変わってきます。
「予約時の挙動についてなんだけど」「それって宿泊者の話?ホストの話?」となるようでは、単語に対して一意に意味が定まらずユビキタス言語は成立しません。
実装としても予約関数の処理が宿泊者かホストかをif文で判別するような、煩雑なものになっていることでしょう。

そういった際はコンテキストを区切り、特定のモデルが適用できる範囲を制限することで解決することができます。
簡単に言えば言葉が変わってくる範囲でシステムを分けちゃうって感じですね。

これをDDDでは境界づけられたコンテキストと言います

同じ図などを見ながらドメインモデルを洗練させていく

こうして齟齬なく会話できるようにした上で、実際に会話を行いながら、ドメインモデルを図などで表現し、洗練させていきます。

ここで大事なのはビジネス側の人が必ずしも自分の業務の全てを理解しているわけではないということです。
たとえば複数の担当が自分の作業を単なる手順として丸暗記してるだけの可能性もあります。
したがって誰かが一方通行に話した内容をただまとめるだけでなく、お互いに作り上げていく意識でドメインモデルを洗練させていくことが重要になります。

ドメインモデルを表現するためのアーキテクチャ

さて、ここまでの流れを通じて、ビジネスを適切に表現できた良いドメインモデルを得ることができたとしましょう。
次はそれを、実装に落とし込む必要があります。

変化に追随できるアーキテクチャ

ドメインモデルは最初にカチッと決めきるのではなく、洗練させていくものです。
そのためDDDではドメインモデルの変更が容易なアーキテクチャが求められます。

変更を容易にするためには、依存関係を切り離す必要があります。
そのためには、あえて冗長な書き方になることもあります。

つまり冗長性と変更可能性のトレードオフにおいて、ドメインモデルに追従するため変更可能性を選択したのがDDDとも言えます。

レイヤードアーキテクチャ

変更可能性をあげるためにまず重要なのは、レイヤードアーキテクチャです。

実際に、オニオンアーキテクチャやクリーンアーキテクチャと言った言葉や、それを表した図を見たことがある人は多いかと思います。

レイヤードアーキテクチャと関連して、「関心の分離」「依存関係逆転の法則」など色々言われることがあるでしょう。
ただDDDにおいてレイヤードアーキテクチャが一番やりたいことは、「ドメインの分離」です

ドメインの分離

ドメインの分離とは、シンプルにドメインとそれ以外を分けようよということです。
「ビジネスに関連する処理が散らばってたらどこ見れば分からなくなるから、1箇所にまとめない?」って感じですね。
たとえばユーザモデルに関する変更をしたい時に、どこにその処理があるか分からない状況と、Userクラスだけ見れば良いのとでは保守性が全然違いますよね。

ドメインの分離の効果

ビジネスロジックはを他から切り離すことで、技術スタックの変更もしやすくなります。

たとえばデータベースをMySQLからNoSQLに変更する、フレームワークを変更するといったときに、クエリにビジネスロジックが入っていたり、フレームワーク依存のビジネスロジックがあると移行は大変です。
一方でドメインが分離されていれば、ドメインロジックに影響を与えることなく移行することができます。

ドメインの分離ができていない例

レイヤードアーキテクチャ自体が目的化してしまうと、ディレクトリ構成をそれっぽくすることだけに意識が向きがちです。
すると、一番やりたいドメインの分離ができてない状況に繋がっていきます。

たとえばクエリにビジネスロジックが入っていたり、バッチファイルなどの外部にドメイン知識があるなんてことがあります。
モデルの初期値をデータベースのDEFAULT値に頼ってるなどもドメイン知識が散らばっている例でしょう。

CREATE TABLE `users` (
   ...
   `state` varchar(255) DEFAULT "active",

ドメインモデルの表現

ドメイン層を分離した次は、実際にドメインモデルを記述していきましょう。

モデル駆動設計

DDDでは、ドメインモデルを適切に表現するために、手続き型ではなくオブジェクト指向で表現していきます。
オブジェクト指向で表現することによって、そのモデルに関するロジックを1箇所にまとめることができます。
そのためモデルの性質や振る舞い、そしてモデル同士の関連性をわかりやすく表現することができるからです。

class Library {
    address: Address
    books: Book[]

    rend(book: Book, user: User) {
        ...
    }
}

class Book {
    id: BookId
    name: string
    pages: Page[]
}

...

ドメインモデル貧血症

逆に言えば、ドメインモデルが単なるデータ型であり、手続き型的に処理が行われるのであれば、それは適切にドメインモデルを表現できているとは言えません

たとえば、モデルがgetterやsetterばかりなケースを見ていきましょう。

class User {
  state: string = 'inactive'
  getState(): UserState { return this.state }
  setState(state: UserState): void { this.state = state }
  ...
}

こういったドメインモデルは単なるデータ型として扱われ、手続き型的に処理が行われてしまうことになりがちです。

register() {
    const user = userRepository.get(id)
    user.setState('active')
    user.setRegisteredDate(new Date())
    const bonusItem = itemRepository.get(BONUS_ITEM_ID)
    user.setItemIds([ ...user.getItemIds, bonusItem.getId ])
    userRepository.store(user)
}

なぜならgetter, setterの存在により、どこでもそのモデルに関する処理が書けるようになってしまうからです。

これはドメインモデル貧血症といって、アンチパターンとして良く挙げられるものです。

まとめ

DDDを構成する要素は他にもありますが、ドメインモデルこそがDDDの肝であるとして、ドメインモデルに関連するところをピックアップして解説していきました。

DDDはドメイン知識・ドメインモデル・実装の相互変換をスムーズに行うための手法です。
ドメイン知識とドメインモデル、ドメインモデルと実装、この2つの相互変換のどちらが欠けても効果は薄れてしまいます。

前者を実現するためにはフィードバックを回しながらドメインモデルを作り上げる必要があり、そのために共通言語を用いて一緒に会話していくことが重要です。
後者を実現するためには、ドメインをその他の実装から切り離し、オブジェクト指向により適切にドメイン知識を表現していくことが重要になります。

補足

DDDオススメの学習法

ドメイン駆動設計 モデリング/実装ガイド

DDDとしての入り口は、松岡さんのドメイン駆動設計 モデリング/実装ガイドがオススメです。
100ページ以内なので読みやすく、具体例付きで全体像が理解できます。

ドメイン駆動設計入門

一方ページ数が多くても良いから細かく理解したい場合は、ドメイン駆動設計入門がオススメです。

ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

Discord Community

これらを読んで理解した気になっても、実際にコードを書いてる上で悩む場面は出てきます。
なのでDDD Communityに入り、質問できる環境を用意するのがオススメです。

先ほどの本の著者である松本さんや成瀬さんを筆頭に、日本のDDD界隈をリードしている方々から意見をもらうことができます。

現場で役立つシステム設計の原則

DDDでありがちなのが「Value Object」「Domain Model」「Repository」などの形だけ真似してしまうパターンです。

これらはあくまで手段であり、なんとなく真似すれば良いコードになるわけでもありません。

そもそもオブジェクト指向で良いコードを書くにはどうすれば良いのかというレベルから理解するために「現場で役立つシステム設計の原則」がオススメです。

現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法

DDDを直接解説した本ではありませんが、まずはこの本の内容を理解したほうがDDDについてより理解できるのではないでしょうか。

その他

通称エヴァンス本にいついては少なくとも最初に読む本としてはオススメしません。
難解なので挫折しやすく、具体例も薄いため誤った理解をしてしまう例を多く見ています。

ある程度DDDについて理解を深めた上で、エヴァンス自身の考えを理解したり、さらに理解を深めるために読む本としての使い方が有用です。

エリック・エヴァンスのドメイン駆動設計

エヴァンス本の具体例としての役割は通称IDDD本が勧められますが、これも読む順番的には最後で大丈夫です。

実践ドメイン駆動設計

UML図とドメインモデルの違い

この両者の違いについては、エヴァンス本の34p「ドキュメントと図」あたりに詳しく書かれています。
前提として、エヴァンスは大抵、クラス図かオブジェクト相互作用図を書きながら議論するとあります。
しかし図はあくまで考え方の骨格であり、コミュニケーションのための手段だとしています。
そのため、きっちりコード上の全てのクラスを表現するようなUML図は過剰だとしています。

また、モデルは図ではないと明確に否定しています。
この辺りのニュアンスが難しいので自分の理解ですが、

  • ドメインモデルはドメインに関する本質的な概念を指す。
  • ドメインモデルについてのコミュニケーションをやりやすくするために、図を用いる。
  • ドメインモデルの全てを図で表現できるわけではない。

という感じになるかと思います。

まとめると、

  • 図はドメインモデルを表すためのコミュニケーション手段であり、全てではない。
  • UML図は、図としては過剰な点と不足する点がある。

ということで、UML図とドメインモデルは異なるということになります。


@dorarep
小学生の頃からフリーゲーム作ってました。今はフリーランスでフルスタックエンジニアしてます。