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

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

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

DDDは何をしたいか

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

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

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

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

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

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

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

ドメインモデルって?

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

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

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

※実際にはUML図はドメインモデルではないと明確に否定されています。両者の違いについては補足に記載しています。

従来の設計との違い

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

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

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

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

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

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

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

では、どうすればフィードバックを適切に回すことができるでしょうか。
そのためには、チーム全員がドメインモデルに対して向き合う必要があります

ユビキタス言語

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

なぜチーム内で言語が変わるのか

そもそもなぜ使ってる言葉が変わってくるの?ってことですが、良くあるのが「エンジニアはテーブル名やクラス名で話す」ですね。

特に日本だと日本語でクラス名やテーブル名をつけづらいので、どうしても違う言葉になりがちです。
こうなるとビジネス側はエンジニアの会話がわかりませんし、エンジニア側も「仕様書にある顧客管理番号がコード上のどれにあたるのかわからない」なんてことになってしまいます。

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

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

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

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

そういった際はコンテキストを区切り、特定のモデルが適用できる範囲を制限することで解決することができます。

簡単に言えば言葉が変わってくる領域で別チームにしちゃうって感じですね。

これを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 {
  name: 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つの相互変換のどちらが欠けても効果は薄れてしまいます。

前者を実現するためにはフィードバックを回しながらドメインモデルを作り上げる必要があり、そのために共通言語を用いて一緒に会話していくことが重要です。

後者を実現するためには、ドメインをその他の実装から切り離し、オブジェクト指向により適切にドメイン知識を表現していくことが重要になります。

その他の概念についても、別の記事で説明していければと思います!
また自分の理解で説明しているところも多いため、マサカリあればぜひお願いします!

補足

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

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

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

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

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

まとめると、

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

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


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