僕とコードとブルーハワイ

omega (@equal_001) の日記

Domain Driven Design Quickly Online を読んでいく。 その3

Domain Driven Design Quickly Online を読んでいく。 その1 - 僕とコードとブルーハワイ
Domain Driven Design Quickly Online を読んでいく。 その2 - 僕とコードとブルーハワイ
の続き。


読んでいる本
www.infoq.com


今回は第三章をまとめる。

前章の振り返り

  • 業務ドメインを中心にしてとらえたソフトウェア開発手法の説明をしていた。
  • 顧客とのヒアリングでドメインを洗い出しするときや、ユビキタス言語を作っていく過程とか作成のポイントとか。
  • ユビキタス言語:ソフトウェア開発者とドメイン専門家とのコミュニケーションを楽にするもの・モデルに取り込むべきドメインの中心概念の発見にも役立つ

モデル駆動設計

モデルをコードとして実装してく作業で重要なのは、簡単かつ正確にコードへ落とし込めるモデルの選定。おすすめの設計手法に「分析モデル」と呼ばれるものがある。分析モデルとは、雑にいうと業務ドメインを分析した結果。

分析モデルを作り上げていく上で発生しうる問題:
  • アナリストが予見できなかった問題がモデルに含まれていた場合、開発者はコードへ落とし込むときに独自の決断をして、設計されたモデルとかけ離れた実装を余儀なくされることがあるかもしれない。
  • モデルとコードの乖離が広がる・モデルの情報が欠損する
より良いモデル作成のためのポイント:
  • アナリストとソフトウェア開発者は積極的にそれぞれが持つドメインの知識を共有していく必要がある。開発者もモデリング段階から参加して、ソフトウェアを正確に表現するモデルを作っていこう。
  • 実装とモデルを強く結びつける。
  • コードの変更=モデルの変更を自覚すること。
  • ドメインモデルから設計に使用される用語抜き出し、モデルの要素に責務を割り当てること


結局のところアナリスト、ソフトウェア開発者の持つドメインの知識を共有する場がないと良いモデルを作れないしモデルをうまくコードへ落とし込めないので、ドメインからモデルが作られていく過程をちゃんと共有する仕組みを作りましょう、という話。

レイヤーアーキテクチャ

ビジネスロジックが他のレイヤに混ざるとそのレイヤの変更に引っ張られる形でビジネスロジックが変異してしまったり、全体的な修正を強いられるので大変。
なので、レイヤ分割し、下位のレイヤだけに依存するレイヤの設計が求められる。
つまり、UI,アプリケーション、ドメイン、インフラなどの各レイヤの責務を明確にまとめることで、変更の管理をしやすくしていこうぜ、という話。

  • UI・・・ユーザが情報の入出力ができる部分。
  • アプリケーションレイヤ・・・アプリケーションの活動を調整するレイヤ。ビジネスロジックを含まない。
  • ドメインレイヤ・・・ドメイン情報を含む。心臓。ビジネスオブジェクトを保持する。
  • インフラストラクチャレイヤ・・・他のすべてのレイヤを補助する。ビジネスオブジェクトの永続化を担う。

エンティティ

エンティティとは、雑にいうと一意性。現実の例で言うと、マイナンバーがエンティティにあたる。DB主キーもそう。ドメインモデルにおいて重要なオブジェクト。

エンティティの重要性としては、以下の一文から読み取れる。

重要なのは、システムが一意性の異なるオブジェクトを簡単に区別できることであり、
一意性を保証する属性や振る舞いが同じであれば、そのオブジェクト同士が同
一だと判別できることです。そうでないと、システム全体が台無しになるかも
しれません。

バリューオブジェクト

オブジェクトをエンティティとして作成する場合の注意点として、あるオブジェクトのインスタンスが大量に作成されるとシステムのパフォーマンス低下になるというのが挙げられる。
本書の例だと、Customerオブジェクトは再利用できないため、顧客がいる分だけインスタンスが生成されますよね、とある。

ある属性を含むオブジェクトを識別することよりも、どのような属性を含むかに関心がある場合は、ドメインのひとつの側面を表現するためのオブジェクトであるため、一意性の保証は必要ない。
このようなオブジェクトをバリューオブジェクトとよぶ。
エンティティを見つけ、そのほかをバリューオブジェクトと定義すると設計は単純になる。

バリューオブジェクトは共有可能なら不変でなければならない。他のオブジェクトが必要とする場合は、値だけを渡すか、バリューオブジェクトをコピーしたものを渡すこと。
なぜ不変でなければならないかという話は、以下の説明で納得した(というか当たり前のことでした)。

変更できるオブジェクトを共有するとどうなるのか想像してみましょう。

例えば、航空予約システムではそれぞれのフライトを表すオブジェクトを作成します。ある顧客がある目的地へのフライトを予約したとします。
もうひとりの顧客が同じフライトを予約しようとします。このときシステムはフライトコードを保持するオブジェクトを再利用します。同じフライトを予約しようとしたからです。
そうしているうちに、顧客は考えを変えて、違うフライトを予約します。このときシステムはフライトコードを変更してしまいます。変更ができるオブジェクトだからです。
その結果、最初の顧客のフライトコードまで一緒に変更されてしまいます。

サービス

動詞は適切な名詞と関連し、オブジェクトの振るまいの一部になる。
オブジェクト指向言語ではドメインに属する振る舞いでも何らかのオブジェクトに属さなければならない。振る舞いは必ずなんらかのオブジェクトに属する。
でも、エンティティやバリューオブジェクトに含めるには的確ではないような、振る舞いがいくつかある。
こういったものをサービスと呼ぶ。


サービスの特徴:

1. サービスとして作成される操作はドメインの概念をあらわしているが、エ
ンティティやバリューオブジェクトに含めると違和感がある。
2. サービスとして作成される操作はドメイン内の他のオブジェクトから参照
される。
3. サービスとして作成される操作は状態をもたない。

サービスは独立したインターフェースとしてモデルに追加し、ドメインモデルの言語を使って定義し、処理の名前もユビキタス言語に含まれなければなりません。

サービスは内部に状態を保持せず、単純に機能だけを提供するものというイメージ。
個人的には、サービスは特定のエンティティやバリューオブジェクトのための機能をまとめるもの、という説明が一番しっくりきた。


モジュール

モジュールは雑にいうとドメインの集まり。
モジュールの各部分が同じデータを操作する場合は通信的凝集になり、モジュール全体にまたがってひとつのうまく定義されたタスクを実行する場合は機能的凝集になる。

ポイント:
- モジュール同士は疎結合にする。
- モジュールへはインターフェースにアクセスさせるようにする。
- モジュール名はユビキタス言語からつける、ドメイン内部に対する洞察を反映するため。
- モジュールの役割は固定、中身は柔軟に。すると設計ミスが見つかった時に修正しやすくなる。


ドメインオブジェクトのライフサイクルを管理するための3つのパターン

アグリゲート

オブジェクトの所有権と境界を定義するのに使うパターン。

特徴:

  • 関連するオブジェクトの集まり(エンティティとバリューオブジェクト)で、データの変更について一括して扱うことができる。
  • アグリゲートの外部からアクセスできる唯一のオブジェクトはひとつのルート(エンティティ)だけ。
  • アグリゲート内部のオブジェクトは互いに参照を持つことができるが、外部のオブジェクトはルートの参照しか保持できない。
  • ルートが削除されメモリ上からも取り除かれた場合、アグリゲート内の他のオブジェクトも削除される。

つまり、アグリゲートという壁を作ってやって、アクセスできるオブジェクトをルートだけに制限することで、不用意なオブジェクトの操作を防ぐ(不変性の維持)、という感じ。
ていうか関連オブジェクト群のカプセル化

データベースのトランザクションはデータの完全性を保証する上でとても重要な役割を果たしますが、もっと望ましいのはデータの一貫性についての問題をモデル内で直接解決することです。

ファクトリ

ファクトリとは、オブジェクト作成のための知識(というか必要な操作群)のカプセル化をするパターンのこと。やってることは、factory_boyのイメージと同じかなぁ。

特徴:

  • アグリゲートに含まれるすべてのオブジェクトを作成するファクトリをつくることで、不変性を保つ。
  • バリューオブジェクトはあとから変更できないのでファクトリ適用時にすべての属性を有効な値に初期化する必要がある。適切に生成できない場合は例外を発生させて無効な値が返らないようにすること。

リポジトリ

まず背景となる問題について。
アグリゲートのルートへバリューオブジェクトのアクセスを要求すると、クライアント側でルートの参照を保持しないといけない。オブジェクトはもともと保持しなくてもいいかもしれないオブジェクトへの参照を保持しなければならず、オブジェクト間の結合が強くなる。
バリューオブジェクトならエンティティの関連をたどりDBから直接アクセスできるけど設計に悪影響を及ぼす。

以下のような悪影響がある:

  • ドメイン全体にデータベースへアクセスするコードがまき散らされ、ドメインモデルが汚くなる。
  • 本来はドメインの概念だけを扱えばいいのに、インフラストラクチャに対する細かな処理をしないといけなくなる。
  • DBの変更をした場合、それにひきづられるようにドメインモデル内にばらまかれたデータアクセスのコードを全て修正することになる。(あー、身に覚えが...)
  • DBに直接アクセスしてオブジェクトを取得すると、クライアントは取得したオブジェクトをアグリゲート内に戻せる。つまり、アグリゲートのカプセル化が破綻する。
  • ドメインにあるはずのロジックがクライアント側のコードに浸透すると、ドメインレイヤが薄くなり、ドメインレイヤとドメインモデルの関連性がなくなる。ドメインモデルとはなんだったのか、という感じになる。

上記解決方法:

  • リポジトリパターン(オブジェクトの参照を取得するのに必要なロジックを全てカプセル化するためのパターン)」を使う。
    • オブジェクトをリポジトリに保存 -> クライアントはリポジトリにオブジェクトを要求する。DBのオブジェクトとクライアントとの仲介役みたいなもの。
    • リポジトリがオブジェクトを持っていない場合は、リポジトリがDBからオブジェクトを取得する
    • オブジェクトのdelete/saveなどの問い合わせ処理をカプセル化する。
    • リポジトリに直接アクセスできるのはアグリゲートのルートだけにすること。そして、オブジェクトの操作はリポジトリに委任すること。
  • 取得条件を仕様として定義する。
    • ファクトリは”新規”オブジェクト作成の管理、リポジトリは”既存”オブジェクトの管理という関係がある。つまり、オブジェクトはコンストラクタを使って作成されるか、ファクトリを通じて作成される。
    • ファクトリはドメインの要素しか持っていないが、リポジトリはインフラストラクチャ(例えばDB)と関連がある。

第3章のまとめ

  • 実装とモデルを強く結びつけるためには、アナリストとソフトウェア開発者は積極的にそれぞれが持つドメインの知識を共有していこう。
  • オブジェクト指向を活用しよう。
  • 各レイヤアーキテクチャの役割を明確にしよう
  • エンティティは一意性を表現し、バリューオブジェクトはエンティティから辿れる不変かつ共有可能なオブジェクト
  • どのオブジェクトにも属さないが、意味のある振る舞い(インターフェイス)をサービスと呼ぶ
  • モジュールはドメインの集合
  • ドメインオブジェクトのライフサイクルを管理するための3つのパターンがある
    • アグリゲート: オブジェクトの所有権と境界を定義するのに使う。アグリゲートの外部からはルート(エンティティ)からしかアクセスできないようにし、オブジェクトの不変性の維持を保証する。
    • ファクトリ: オブジェクト作成のための一連の操作をカプセル化するもの
    • リポジトリ: ドメインでやることをクライアントに混ぜないために、クライアントとインフラストラクチャの間に入ってオブジェクトの問い合わせをしてくれたりするやつ