コンテンツにスキップ

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

#ドメイン駆動設計 エヴァンス本

📕 読んだ本

エリック・エヴァンスのドメイン駆動設計 和智 右桂 翻訳 Eric Evans 著

💪 モチベーション

  • TDDやリファクタリング、デザインパターンをやって、DDDの入門系の本を読んでやっと本題といったところですね。
  • ボリュームのデカさやわかりにくさに定評(?)はありますが、やはり原本をちゃんと読んでおくことは、最初にDDDの話をはじめたエリック・エヴァンスの思考のプロセスをなぞることができるので読んでおくべきといったところで、読んでみました。

🕐 かかった時間など

  • 2022/12/15 〜 2023/1/30
  • スタディプラスの記録によると、34時間くらい。
  • 500Pくらいありますが、手を動かす系の本ではなかったので、OS自作本と比べたら、意外にもさらっとおわった気もします。
  • とはいえ、大変でした。

😄 理解メモ(復習)

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

  • 知識の噛み砕き
    • 効果的なモデリングの要素
      1. モデルと実装を結びつける
      2. モデルに基づいて言語を洗練させる
      3. 知識豊富なモデルを開発する
      4. モデルを蒸留する
      5. ブレインストーミングと実験をおこなう
    • つまり、ドメイン知識を噛み砕いて深く理解し、コードに落とし込むことが大事。
    • ドメインは複雑。役に立つモデルは表層にないことが多い。モデルの蒸留、噛み砕きが必要で、そこに注力したい
    • 開発体制について
      • 仲介者的にアナリストが入ると、コーダーにドメイン知識が入りくいので、必然的にコードにもドメイン知識が反映されにくい
      • また、アナリストが仲介すると、開発者がモデルをつかったり、小さくつくったものをみせてフィードバックを得て改善していくプロセスが得られない
      • 外注すると、コードは納品されてもドメイン知識と技術を理解した人が他のところに異動してしまう。
  • ユビキタス言語について
    • 最初は、ユビキタス言語をつかってもうまくドメインを表現できずにモデルも表現できないかもしれない。どちらが先かの因果関係も捉えにくい
    • 粘り強くユビキタス言語をつかうことが重要。違和感があれば言語自体を改善していく。これがドメインモデルにも反映できる
    • ドメインエキスパートは、ドメインについて不適切だったり使いにくい用語や構造に意義をとなえる
    • 開発者は、設計を妨害することになる曖昧さや不整合に目を光らせる
    • 要求と設計は分離して考えるべきというのもあるが、要求はプロセスを経て研ぎ澄まされていくもの
  • ドキュメントについて
    • 「XPの美学=すべてはコードに語らせる」のスタンスのうえで、ドキュメントはコードや会話の表現を補わなければならない
    • 手書きの図は、むしろ形式張らないでその場かぎりのコミュニケーションであるという印象を示すことができる
    • ドキュメントを最小限にとどめ、その焦点をコードとの会話の補足に限定することでドキュメントがプロジェクトに結び付けられた状態を保つことができる
  • モデル駆動設計
    • 分析が技術的な配慮から妥協した貧弱なものであってはならない
    • ドメインに対する考え方を反映しているが、ソフトウェア設計の原則を避けた出来の悪い設計も受け入れられない
    • 「ユーザモデルと設計・実装モデル」の差異を、開発者をドメインエキスパートとの間で埋めていく
    • 技術的な理由が背景になることを説明するかどうか迷うこともあるかもしれないが、それにより、ユーザがソフトウェアの潜在能力に触れられるようになり、ユーザが一貫した予測可能なものになるので必要

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

  • レイヤードアーキテクチャ
    • 3層 + ドメイン層
    • コールバックやオブザーバー(デザインパターン)によってレイヤー同士を疎結合にする。DI、MVC、いろいろあるが分離さえできればどんなアプローチでも構わない
    • フレームワークは利用する際に、ドメインの設計を制限する前提を多く設けすぎてしまうものでないか注意が必要
  • アンチパターン「利口なUI」
    • 利用なUIにしちゃったほうが簡単だし、レイヤードアーキテクチャにするには学習コストや文化を根付かせるコストがかかり、その利点をうけられるのが僅かなように感じられる
    • アプリケーションの統合は困難で、データベースを経由させるしかなくなる。
    • 迅速な開発ができても、ビジネスロジックの抽象化がかけているためリファクタリングが進まない
    • 複雑さにすぐに覆い尽くされて、改善がしにくくなる
  • ソフトウェアでモデルを表現する
    • エンティティ
    • 値オブジェクト
    • サービス
    • モジュール
  • ドメインオブジェクトのライフライクル
    • 集約
    • ファクトリ
    • リポジトリ
  • 7章 → 貨物輸送システムの例

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

  • 8章.ブレイクスルー!!
    • 銀行のシンジケートローンを管理する巨大アプリケーションでの例
    • ファシリティの分担率とローンの分担率が別であるということに気が付き、シェアバイという概念でモデルがかなりシンプルになった。
    • ブレイクスルーがおきるのは、適度なリファクタリングを何度も行ったあとだった。
    • 僅かな改良をためらわずやり、あまりに遠い先をみて麻痺しないこと
    • そこからさらにどんどん設計がよくなった。トランザクションとポジションという概念がうまれるブレイクスルーが新たにおきた。
    • 多くのプロジェクトは、この段階になると、すでに構築したものの量と複雑さに足を取られて身動きができなくなるのだが、このプロジェクトではどんどん加速することになった。
  • 9章.暗黙的な概念を明示的にする
    • 概念を掘り出す
    • 言葉に耳を傾ける
    • ぎこちなさを精査する
    • 矛盾について熟考する
    • 文献を読む
    • 何度も挑戦すること
  • 「仕様」パターン
    • 検証
    • 選択
    • 要求に応じた構築(生成)
  • 10章.しなやかな設計
    • 複雑なふるまいをするソフトウェアの設計が優れたものでないと、リファクタリングは進まない
    • レガシーさによって保守が難しくなる状態を避け、楽しく仕事ができて変更を呼び込むような設計(しなやかな設計)が欠かせない
    • 多重レイヤーなどのオーバーエンジニアリングによって開発がスムーズにいかなくなることがある。シンプルなほうがよい
    • シンプルにつくるのは難しい。適度に厳密な設計スタイルが必要
    • しなやかな設計に求められるもの
      • クライアント開発者が幅広いシナリオを組み立てることができるようにつくること
      • 設計自身を変更しようとする人にとっても役に立つものにすること
  • レガシーの保守に飲み込まれないためのプラクティス集
    • 意図の明白なインタフェース(Intention-Revealing Interfaces)
      • 名前だけで意図がわかるようにする
    • 副作用のない関数(Side-Efect-Free-Functions)
      • 副作用のない操作は、メソッドではなく、関数(Function)と呼ばれる
    • 表明(Assertions)
      • 副作用があるものについては、どんな副作用なのかを明らかにする
    • 概念の輪郭(Conceptial Contours)
      • 設計における粒度は、細かすぎると呼び出し側にロジックの組み立てを強制し、複雑化し、粒度をそろえるのにも苦労する。
      • 技術駆動のリファクタリングではなく、ドメイン駆動のリファクタリングで概念の輪郭でちょうどよい粒度を模索する
    • 独立したクラス(Standalone classes)
      • 最も入り組んだ処理を独立したクラスとして分解するように試みる。
      • これは、他とつながりの深い値オブジェクトをモデル化すると実現できそう
      • あらゆることを恣意的にプリミティブにして、結合度を下げてモデルの表現力をさげることは避けたい
    • 閉じた操作(Closure of Operations)
      • 数学の加算の概念や、XMLがXSLTで表現される再帰的な閉じられた集合としての概念がある
      • つまり、戻り値の型が引数の型と同じにできる場合は、そのように操作を定義すること
      • 値オブジェクトの操作では、このパターンが最も頻繁に適用できる
  • 設計の宣言的スタイル
    • 論理演算子(AND、OR、NOT)といった演算子をつかって組み合わせることができ「閉じた操作」にできる
    • 重要で一般的な仕様があらゆる種類の仕様につかえるということであればGenericsをつかって抽象化ができる
    • 抽象化した仕様に論理演算向けのメソッドを追加すると使いやすくなる
  • 13章. より深い洞察へ向かうリファクタリング
    • 開始
      • コードの持つぎこちなさをや、複雑さへの対応から始まる
      • その原因がコードのつくりの問題でなく、ドメインモデルにあるかもしれないと考えることから始まる
    • 探求チーム
      • ユビキタス言語の駆使
    • 先達の技
      • アナリシスパターンのような書籍を参考にするのも有効。車輪の再発明を避ける
    • 開発者のための設計
      • しなやかな設計による開発者への恩恵を侮るべからず
    • リファクタリングを積極的にすべきタイミング
      • 設計がドメインに関するチームの現在の理解を表現していない
      • 重要な概念が設計で暗黙的になっている
      • 設計において重要な部分を、よりしなやかにする好機がある
  • 問題がおきているときにドメインとマッチしていないことに気づくことがあり、それは好機なのだが、むしろ危機と捉えられることが多い → 好機ととらえよう!

第四部:戦略的設計

  • 規模が大きくなっても、ドメイン駆動設計が実装と結びつかないモデルを作り出すことはない
  • 3つの原則
    • コンテキスト:ドメインを矛盾が生じないように境界づける
    • 蒸留:最も付加価値のある特別な側面を目立たせて、その部分にできる限り多くの力を与えるような構造にする必要がある
    • 大規模な構造:全体像をつくる。木を見て森を見ずにならないようにする
  • 大規模開発でドメイン駆動設計に取り組むときの課題
    • エンターブライズシステムの中で、車輪の再発明は避けたいが、それらの相互の関係がどうなるかについて、慎重になる必要がある
    • 巨大なシステムのドメインモデル全体を完全に統一することは、現実的でなく、コストにも見合わない
    • 単一のモデルをつかって万人を満足させようとすれば、選択肢が複雑になり、モデルを使うのが難しくなる
  • 境界づけられたコンテキスト
    • 巨大なプロジェクトであれば、複数のモデルが存在する。境界を認識せずにそれらを混ぜ合わせると、バグの温床となり、信頼できなくなり、理解しにくくなる
    • 境界の引き方は、通常チームが編成されて人々が交流するやり方で決まり、その方法だと問題が生じる
    • コンテキストの明確な境界は、アプリケーションに特有の部分が持つ用途、コードベースやデータベーススキーマなどの 物理的な表現などの観点から設定すること。
    • 別なコンテキスト同士は、用語法、概念、ルール、ユビキタス言語の方言が違う
    • 継続的な統合(Continuous Integration)
      • コンテキストを細かく分けすぎてしまうと、ドメインへの理解が進んだ後の統合が難しくなる
        • 自分の取り組んでいる概念が、別のモデルにあることに気づかず、重複してしまう
        • 既存の機能を壊すことを恐れて手を加えられない
      • 継続的な統合は実装と対になる。以下の環境を整える
        • CI/CDによる継続的なデプロイ
        • 自動化されたテストスイート
        • 統合する期間を設定。統合されない期間に対して妥当な短さの上限を設定。できるだけトラ ンクベース開発に
    • コンテキストマップ(Context Map)
      • 境界づけられたコンテキストにそれぞれの名前をつけ、その名前をユビキタス言語の一部にす る
      • コミュニケーションに必要な変換を明示的にして、共有するものは強調する
      • コンテキストマップはありのままを表すように頻度高くメンテナンスする
      • コンテキストマップ内の矛盾には、果敢に取り組んで調整して、明示的にする必要がある。こ のとき、明らかな間違いから取り組み、小さくステップを踏みながら、変更を適用していく
      • コンテキストの境界をテストで保護するのは重要
      • 境界づけられたコンテキストには、それぞれかならず名前をつける
      • 全員がどの境界があるのかを知っている必要があり、コードのどの一部をとってもコンテキス トを認識できなければいけない
    • 共有カーネル(Shared Kernel)
      • それぞれ別なチームが1からものをつくってしまうと、「継続的な統合」を行うよりも多くの時 間が必要になってしまう危険がある。
      • 2つのチームが共有することに合意したドメインモデルのサブセットを指定する。
      • 共有カーネルは、設計の他の要素ほどは自由に変更できない
      • 共有カーネルは、コアドメインであることも多いし、複数の汎用サブドメインの集合であるこ ともある
    • 顧客/供給者の開発チーム(Customer/Suppier Development Teams)
      • 上流のサブシステムと下流のサブシステムでは、上流のサブシステムが下流への影響を考慮し て安易に変更できないことがある
      • ここで、明確な顧客と供給者という関係を作っておき、連絡手段、提供スケジュールの約束を チーム全員が理解できるような体制をつくる
      • 共同でテストを統合して、「継続的な統合」=CI/CDの一部として実行されるようにする
    • 順応者(Conformist)
      • 上流/下流の関係にあるにも関わらず、上流との関係がうまく行かない背景にある場合、下流 が自分でなんとかするしかない
      • 境界づけられたコンテキスト間の複雑な変換を取り除き、上流にできるだけ合わせることで大 幅な簡略化を見込む
      • 上流モデルに強く依存するので、実際には選択すべきときに選択できないことが多い
      • 順応者は、共有カーネルに似ているが、違う点は、上流・下流の協力具合。下流が一方的に順 応しなければならない点。
    • 腐敗防止層
      • コンテキスト境界の変換層が複雑になり、巨大になり、その場しのぎになりということは、十 分ありえること
      • インフラストラクチャ層の技術的な違いを吸収するのは、本来の腐敗防止層の役目ではない
      • FacadeやAdapterパターンを適用する
      • 統合の要件が広がってきて、変換のコストがかなり大きくなったときには、順応者パターンへ 舵をきる判断も必要
    • 別々の道(Separate ways)
      • 統合は高くつくが、それによる利益は小さいことがある
        • 統合が必須であるということではない
        • 境界づけられたコンテキストを他とは一切つながりを持たないものと宣言する
    • 公開ホストサービス(Open Host Service)
      • サブシステムに対する需要が高いとわかったときには、より柔軟なアプローチが必要
      • サブシステムとほかのサブシステムとの連携でそれぞれに変換サービスをつくっていると、保 守すべきものが増えていき困ることがある。
      • 需要の高いサブシステムにプロトコルとしてのサービスを集合として定義する。
      • 公開ホストサービスとして、つくるのは、複数のサブシステム向けの共通したもの。あるチー ムにだけ特有の要求は従来どおり変換をつかうこと
    • 公表された言語(Published Language)
      • 2つのドメインモデルを共存させ、その間での情報のやりとりをすることが必須であるならば、 変換プロセス自体が複雑化することもある。
      • 公開される言語として、公開ホストサービスのように一般的な言語を公表する
  • 『盲人と象』John Godfray Saxe 1816-1887
    • 統合する必要がなければ、それぞれが別々に扱っていても問題にならないが、統合が必要であれば、互いに意見が一致していないということを認めるだけで多くの価値が得られる
    • モデルを統一するのは難しい。誰も自分のモデルを断念して他の人のモデルを採用しそうにない
    • 更に、同じ部位(鼻)に対して違う意見(消防ホースなど)も混ざっているとさらに難易度がます
    • 複数のドメインモデルがぶつかり合っていることを認識することは、単に現実と向き合っているにすぎない
  • モデルコンテキスト戦略
    • チームが独立して活動することの価値と直接的で豊かな統合の価値との間で考えるべき
    • かかるコストの一部を見積もって伝えそれを軽減する方策を講じながらやる。コンテキストマップから初めて、組織構成の変更は現実と向き合ってやること
  • 蒸留
    • 蒸留:混ざりあったコンポーネントを分離するプロセス
    • コアドメイン:蒸留によって、抽出したい特別に価値がある1つのコンポーネント
  • 蒸留による効果
    1. システム全体の設計と、設計同士の関係をチームメンバー全員が把握できる
    2. ユビキタス言語に入れやすいサイズのコアモデルを識別できる
    3. リファクタリングの指針となる
    4. モデルで最も価値がある領域に作業を集中させる
    5. アウトソーシング、既製のコンポーネントの利用、その割当について決定する際の指針となる
  • 大規模な構造
    • アーキテクチャがドメインモデルの領域に乗り出すとそれ自体が足枷になることがある
    • 指針となるルールに問題があるのではなく、ルールの厳格さや成り立ちに問題があり、それが適切になれば、開発の妨げになるどころか役立つものになる
    • 概念上の大きな構造は、アプリケーションとともに進化させる必要がある
    • システムのメタファ(System Metaphor)
      • Firewall、レイヤー、カーネルといったメタファを活用
      • メタファをつかうことによる理解の齟齬が逆にうまれるかもしれない
    • 責務のレイヤ(Responsibility Layers)
      • 依存関係から上位、下位を与えることができる
      • ドメインの部分の変更についてその変化の割合を原因も調べる。
      • 自然と生まれた階層があれば、それらの責務を明確にする
    • 知識レベル(Knowledge Level)
      • 知識レベルとは、他のオブジェクトグループがどうふるまうべきかを記述するオブジェクトグループ
    • 着脱可能コンポーネントのフレームワーク(Pluggable Component Framework)
      • 抽象化されたコアを蒸留し、そのインタフェースの多様な実装を自由に置換できるようにするフレームワークをつくること
  • 戦略的設計上の意思決定を行うために欠かせない6つのこと
    1. 意思決定はチーム全体に伝えなければならない
    2. 意思決定プロセスはフィードバックを吸収しなければならない
    3. 計画は進化を許容しなければならない
    4. アーキテクチャチームが最も優秀な人材をすべて吸い上げてはならない
    5. 戦略的設計にはミニマリズムと謙虚さが必要である
    6. オブジェクトはスペシャリストだが、開発者はジェネラリストである

🎉 感想

  • いやぁ、むずかしい本でしたが、ちんぷんかんぷんで理解できないということではなく、「とても重要なことを言っている」という感覚は得られましたw。
  • とはいえ、特に第四部は、理解できたかどうかかなり怪しいです。
  • 発行自体は古い本なのですが、アジャイル開発やDevOpsとの関連を強く感じる内容だったのはひとつ驚きでした。
  • 予想よりも第四部の分散アーキテクチャの部分がかなり力をいれてボリューミーに書かれていた印象を受けました。これまで私が読んだドメイン駆動設計関連の本ではあまり出てきていなかったので、原本ではこうなのか、という印象がありました。
  • 明快な「これを実践すれば間違いない」というメッセージよりも、現場のつらみやあるあるの課題から得た、よかったプラクティスを紹介してあり、それを紹介しつつも「とはいえこういう別な課題もあるよ」「こういう意味だから解釈間違えないでね」というメッセージの論調が多かった印象です。
  • つまり、本には具体的なプラクティスが書かれているのですが、むしろそれにいたる考え方を学ぶことができ、アジャイル、TDD、アーキテクチャ、DevOpsをやっていく理由やつながりを感じながら、設計へのスタンスのあり方を感じられる一冊だったかもしれません。
  • これで原書を読んでなにが書かれていたのか一旦頭に入れておくことはできたので、他のドメイン駆動設計に関する本を読むときに、どう解釈すべきなのか答え合わせをすることはできそうです。
  • これを実践できるようになるのは、だいぶ先な感覚がありますが、小さくステップを踏んでいくしかないですね。

🔭 今後の展望

  • 一旦、整理やまとめ、解釈の答え合わせとして、以下のSoftware Designのドメイン駆動設計特集も読んでおきたいと思います。
Software Design 2023年2月号
  • iDDD、PoEAA、クリーンアーキテクチャも読むとさらにDDDの理解が深まりそうですが、先は長いですね。
  • まだあまり良くわかっていないデータ中心アプローチのほうもスタディしたいです。まったくわかっていないので、こっちのほうが優先度高めでしょうか。イノシシ本。かなりボリューミーです。。

最終更新日: April 29, 2024