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

はじめに

DDDの教科書的な文献であるエリック・エヴァンス本を改めて読み直したので、実践で意識しておくべき点を整理してみました。

ドメイン駆動設計はオブジェクト指向の最終到達点とも呼ばれることもある重要な設計パラダイムです。

自分自身、ソフトウェア開発をする上で、DDDと出会ったことで大きく変わりました。良い設計・コードとは何かということを迷わずに考えていけるようになり、プログラミング自体もはるかに楽しく感じるようになりました。

参考文献

自分は主にこれらの書籍からDDDを学びました。いきなりエリック・エヴァンス本だと骨が折れるので、初めてDDDに触れる方は2つ目の本が分かりやすくておすすめです。

補足:

本業の方でソフトウェアアーキテクチャや設計に関する研修講師を担当したんですが、そこでDDDについての概要の解説も行いました。

スライド:ソフトウェアアーキテクチャ研修【MIXI 25新卒技術研修】 - Speaker Deck 動画:https://www.youtube.com/watch?v=u3HhiticY4o

最近そのスライドが公開されて、結構力を入れて作ったやつなので、思ったより見てもらえているようでありがたいです。

上のスライドでは、DDDについて概要やデザインパターンについて簡単に紹介した程度なので、本ブログでは続きとして、実践する上で大事な考え方をエリック・エヴァンス本を元にもう少し細かいレベルで書いておこうと思います。

一方で、DDD自体の概要やデザインパターンについてはこちらのスライドで解説書いているのでこのブログでは省略します(モチベあればそちらも参照してもらえると)。

第1部 ドメインモデルを機能させる

第1部は「ドメインモデルを機能させる」というテーマで、3章に分かれて解説されています。

1章 知識をかみ砕く

  • ソフトウェアを書き始める時、我々は対象を十分に理解しているわけではない
  • ドメインに対しては継続的学習が重要

効果的なモデリングの要素

  1. モデルと実装を結びつける
  2. モデルに基づいて言語を洗練させる
  3. 開発者、ドメインエキスパート、その他参加者全員が共通の言語を使ってコミュニケーションする
  4. 知識豊富なモデルを開発する
  5. 単なるデータスキーマではない
  6. モデルを蒸留する
  7. ドメインのあらゆる知識・詳細を取り入れるべきではなく、必要なものを選び抜く
  8. ブレインストーミングと実験を行う
  9. ドメインエキスパートへのデモ・対話が大事。この過程でモデルが蒸留されていく

ここで、ドメインエキスパートの方と本書で述べられているほど頻繁に対話する機会が得られないケースもあり得ますが、そこは今ならAI先生に任せることができるかもしれません。

ドメインに寄るとは思いますが、ビッグテックは色んなドメインのデータ集めまくって学習してくれているので。

2章 コミュニケーションと言語の使い方

ドメインエキスパートとの関わり方や会議の仕方に関して大事なことが書かれている。 ここで言われているような内容を、会議の最初に毎回チームで確認すると良いかも。

  • モデルを言語の骨格として使用すること
    • チーム内の全てのコミュニケーションとコードにおいて、その言語を厳格に用いるようにすることを約束する
    • エンジニア視点では、技術用語は使わないよう気をつけること
  • 言語を使う上で問題があれば、取り除く努力をすること
    • 曖昧な理解のままにしない
    • 問題を取り除く新しい表現は代わりとなるモデルを反映しているため、新しいモデルに合わせてコードもリファクタすること
    • コードの中で用語が混同されていたら、認識を合わせること(唯一の用語で統一する)
  • 会議におけるそれぞれの役割
    • ドメインエキスパートは、ドメインについての理解を伝えるには使いにくかったり、不適切だったりする用語や構造に異議を唱えるべき(開発者視点では唱えてもらうようにするべき)
    • 開発者は、設計を妨害することになる曖昧さや不整合に目を光らせるべき
  • モデリングは声に出してみるのが大事
    • シナリオなどを声に出してみると、言語の問題にすぐに気づける
    • 表現するより簡単な用語が見つかれば、それを図とコードに反映させること
  • モデルにおける詳細はコード上でのみ表現する。図やドキュメントは補足として用い、円滑なコミュニケーションのために詳細に書きすぎないのが大事。
    • 詳細についてはすでにコードが担っているので、ドキュメントでも同じことをやるべきではない
  • 出来上がったモデルは教材としての価値を持つこともある
    • 設計を推進するモデルは、ドメインに対する見方の一つ
    • 新しく参加するメンバーに対する説明としても使える

ここで書かれていることが実践できると理想状態に近づけそうですが、結構めんどくさい部分ではあると思います。

ただ、今は便利なことに資料化・資料更新などもほぼAIに全て任せられる時代になってきているので、より実践のハードルは下がってきている印象もあります。

あとは読んでいて、ここの過程を苦なく進めてくれるように、ドメイン自体に興味のある人間(エンジニア)であることも、現実問題として重要だなと感じました。DDDを実践する開発組織の場合、採用の際には技術力に加えてそういった観点も考慮が必要になるかもしれません。

3章 モデルと実装を結びつける

  • コードをその元になっているモデルへ緊密に関係づけることにより、コードに意味が与えられ、モデルがドメインと関連深いものとなる
    • 単なる分析モデルとして扱うこともありうるが、それだけでは費用対効果が薄い
    • DDDでは、分析モデルと設計の二分法を捨て去り、両方の目的に使える単一のモデルを探し出す
  • ドメインモデルを持たず、次から次に機能を満たすコードを作成するだけのプロジェクトでは、これまで述べたような恩恵は受けられない
    • 複雑なドメインが現れたときに手も足も出せなくなる
  • 実装をモデルに結びつけるには、通常、オブジェクト指向プログラミングのようなモデリングパラダイムをサポートするツールやプログラミング言語が必要
  • 開発は、モデルと設計およびコードを単一の活動として改良し続けるイテレーティブなプロセスとなる
  • 設計・モデリングとプログラミングを厳密に分離・分担するべきではない
    • モデルが正しく引き継がれず理解されなくなり、コードにも反映されなくなる
    • モデルが実装や技術と相互作用した時のフィードバックも受けられなくなってしまう
    • 仮に分けたとしても、モデルに貢献する技術的な人は誰でも、一定の時間をコードに触れることに費やさなければならない

第2部 モデル駆動設計の構成要素

4章 ドメインを隔離する

  • オブジェクト指向プログラムでは、ユーザインターフェース(UI)やデータベース操作に関するコードがビジネスオブジェクトに書かれることがしばしばある
    • 実装上簡単であるため。ただし、それらが混在するとモデルの理解が困難になる
  • ソフトウェアを分割する方法としては、レイヤ化が一般
  • アプリケーション層にビジネスルールが書かれがちだが、ドメイン層に集中すべき
  • レイヤ同士は疎結合であるべきで、依存関係は上から下の1方向のみ許可される

アンチパターン:利口なUI

ユーザーインターフェースドメイン層を分離しない。UIが知識を持ってしまう。

  • 利点
    • 単純なアプリケーションの場合、生産性が高くすぐに作れる
    • 設計に関する知識など、ほとんど訓練しないで仕事ができる
    • アプリケーションが互いに分離しているので、小さなモジュールの納品スケジュールは比較的正確に計画できる
    • 新しくJoinした保守プログラマは、自分が理解できない部分を好きに作り変えられる。変更による影響がそれぞれ特定のユーザインターフェースに限定されるため。
  • 欠点
    • アプリケーションの統合は困難で、DBを経由させるしかない
    • ふるまいが再利用されることも、ビジネスの問題が抽象化されることもない。ビジネスルールは適用先の操作それぞれで複製されることになる
    • 次第に迅速なプロトタイピングやイテレーションができなくなる
    • 複雑な機能を新たに追加することが困難になっていく

5章 ソフトウェアで表現されたモデル

モデリングした後は、それを正確にソフトウェアに反映させることが重要です。 モデリングとコードが乖離していたらモデリングという操作が(ほとんど)意味のないものになってしまいます。

そのためのパターンについて解説されてますが、こちらに関しては最初に補足で紹介したスライドに書いているので省略します。

6章についても同様です。

7章 言語を使用する:応用例

ここでは第2部で解説があったパターンを使った具体的な開発や検討の流れが書かれています。長くなってしまうのでここでは省略します。

シナリオベースで実際にどのようにモデリングしたり、パターンを適用するかが書かれているので、具体例を知りたい方は読んでみると良いかもしれません。

第3部 より深い洞察へ向かうリファクタリング

リファクタリングにも色々あるが、技術的なリファクタリングではなく、モデルの改善やより深いモデルに近づくためのリファクタリングの重要性や方法を書いている。

ここでは一部の章だけ取り上げます。

8章 ブレイクスルー

より深く正確なモデルを発見でき、システムをはるかにシンプルに考えられるようになることをブレークスルーと呼んでいる。

本章では著者の実際のプロジェクトで発生したブレークスルーの話があり、同じ機能であってもモデル(設計)によって全く複雑度が違うことが分かる。

特に、良いモデルを発見できることによって、はるかに設計や実装が簡単になっていったことがわかる。

その他読んでいて大事だと思った要点もメモしておく。

  • 設計したモデルがドメインエキスパートにとって理解に苦しむようなモデルである場合、ドメインの理解が不十分かもしれない
  • ドメインエキスパートが使わない用語・概念であるにも関わらず、技術者の都合で追加された用語には注意が必要
    • 勝手に不必要な概念を追加して設計してしまうことを招くかもしれない
    • 用語については一つ一つドメインエキスパートに確認するべき
  • ブレークスルーを引き起こそうとして麻痺するべきではない。ブレークスルーが起こり得るのは何度もリファクタを繰り返した後
    • 小さなリファクタを繰り返すことによってモデルは徐々に深まる
    • とはいえ、最初のモデリングをしっかりと行うことで多くの手戻りをなくせるため、そこは一定頑張る価値はありそう。
  • ブレイクスルーの機会が発生した際に、変更が本当に可能か検証できるようにするために、シナリオケースをたくさん用意しておくと良さそう。新しいモデルに対してそれぞれのシナリオをウォークスルーすることで、検証する
  • ブレイクスルーによって既存システムがシンプルになることで、新しく複雑な機能の追加に耐えられるようになる

9章 暗黙的な概念を明示的にする

  • ドメインエキスパートの使い言葉で、複雑なものを簡潔に述べている用語があればそれを抽出するべき
  • 会話の際、開発者、ドメインエキスパート、ユーザが設計のどこにもない語彙を使用していたら要注意 => 設計改善の好機
  • 制約はあらゆるオブジェクトに存在する可能性があるが、オブジェクト内で制約を実装するとオブジェクトの設計が歪められてしまう恐れがある
    • 制約を評価するために、オブジェクトの定義に合わないデータが必要になってしまう可能性がある
    • 関連するルールが複数のオブジェクトに出てきて、コードの重複が増える
    • 設計や要求に関する多くの会話が制約をめぐって行われるが、実装では制約が手続きとしてコードの中に隠されることが多い
  • 制約も一つのオブジェクトとして明示化すると良い
    • オブジェクトやメソッドの内部に直接書かれることもあるが、複雑な場合は切り出すことで、振る舞いをシンプルに保てる
      • => 仕様(Specification)パターン
    • 別の関数として命名することでその制約自体の議論もしやすくなる
  • 仕様とは、あるオブジェクトが何らかの基準を満たしているかを判定するもの
    • エンティティや値オブジェクトの責務として合致しないビジネスルールも多いが、そういうものを表現する時によく使えるのが仕様

10章 しなやかな設計

本章では、柔軟で変更しやすい設計にするために必要な要素が挙げられています。

  • 意図の明白なインターフェース
    • あるコンポーネントを開発した人とは別の人がオブジェクトや操作の目的を推測する上で、実装を確認しなければならないとしたら、カプセル化の価値は失われる
    • クラスと操作には、その効果と目的を記述する名前(ユビキタス言語に従っていなければならない)をつけ、約束したことを実行する手段には言及しないこと(クライアント側はインターフェースの内部を知らなくて良くなる)
    • 振る舞いを実装する前にそのテストを書いて、自分がクライアント視点で考えられるようにすると良い
  • 副作用のない関数
    • ある振る舞い(コマンド)を実装する上でも、出来るだけ多くの部分を副作用のない関数に分けること
    • 値オブジェクトに委譲できるものは出来るだけそうする
  • 表明(Assertions)
    • 矛盾したコードが作られるリスクを小さくする
    • 操作の事後条件や、クラスおよび集約の不変条件を宣言すること
    • プログラミング言語でassertionを対応できない場合は、ユニットテストやドキュメントに記述すること
  • 概念の輪郭(Conceptual Contours)
    • クラスやメソッドを分割しすぎても、クライアント側を無意味に複雑にしてしまうことになる(組み合わせの理解を強制してしまうため)
    • 概念の輪郭を意識して分割すること
  • 独立したクラス
    • 疎結合はオブジェクト設計の基本
    • オブジェクトのイメージの中から他の概念を徹底的に取り除くこと
    • 自己完結型のクラスを目指す

第4部 戦略的設計

ここから先はさらに細かい話が多いので、より適用場面が多そうなものだけ整理しておきます。

14章 モデルの整合性を維持する

  • 境界づけられたコンテキスト
    • 同じ概念でもそれが適用されるコンテキストによって解釈が変わることがあり、複数のモデルが生まれることがある
    • それらが組み合わさったり、区別されずに議論されるとバグの温床となる
    • モデルが適用されるコンテキストを明示的に定義して境界を作り、別々に扱うこと。境界内で厳密に一貫性のあるものに保つこと。
    • コンテキストマップを作っておく

15章 蒸留

  • 蒸留:混ざり合った複雑なコンポーネントから、価値があって役立つ形式で本質を抽出すること
  • コアドメイン
    • アプリケーションにとって極めて重要なコアモデルを洗練させていくことが重要(モデルの洗練にも優先順位をつける)
    • コアドメインの蒸留は簡単ではない => 最も優秀な人をコアドメイン開発に割り当てることも大事
    • コアドメインに絡むサブドメイン(コアの概念に絡むが補助的な役割を果たすもの)も分離していくことが重要で、分離して優先度をコアより下げることで、そこに対するコアドメインの開発者のリソースを取り除ける
    • コアドメイン以外はアウトソーシングも考えられる(逆にコアドメインをアウトソースしてしまうのはアウト)
  • ドメインビジョン声明文というものを作成しておくとよい
    • 開発の流れの中で、コアを見失いづらい
  • 8章で述べられているブレークスルーにおいて、プロジェクト全体の軌道を変えられるほどの価値を生めるのは、コアドメインの時だけ => コアドメインに対する理解を深めるのに時間を使うべき

16章 大規模な構造

ここでは大規模なソフトウェアを構築する上で、アーキテクチャや構造など、何らかの実装上の制約を持たせることにより、大規模になってもソフトウェアが理解できるものであり続けるための方法について述べられています。

よく言われているレイヤードアーキテクチャ、オニオンアーキテクチャとかに限らず、レイヤ化と依存方向整理によってソフトウェアの構造を捉える例を解説してます。

17章 戦略をまとめ上げる

最後の章は、特に第4部で解説された内容をもとに図と共に短く整理されているため、実際にプロジェクトでDDDを実践する際に読み直し、必要に応じて各章で具体例を見にいくという使い方が良いように感じました。

まとめ

最後の方はだいぶ省略しちゃいましたが、エリック・エヴァンス本を通しで読んでみて重要だなと思った点を整理してみました。

エリック・エヴァンス本で述べられていることを全て実践していくのは骨が折れそうですが、取り入れられるものだけでも入れていけばだいぶ変わるとは思います。

何より、本書で述べられているように、どのようなモデルを発見できるかによってソフトウェアの複雑度は全く変わることになるので、そこのセンスは磨いていきたいものです。