8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

読書メモ:ドメイン駆動設計 モデリング/実装ガイド(前半)

Last updated at Posted at 2020-09-22

会社で「ドメイン駆動設計 モデリング/実装ガイド」の読書会に参加しました。
連休中に内容を見返すと、結構忘れていたので、内容をメモをすることで思い出そうと思います。

後半はこちら


第1章 DDD概要

DDDに取り組むにあたり、言葉の定義によって混乱することが多いので、ここで以下のように定義する。

DDDとは

DDDとは、ソフトウェア開発手法の一つ。
ソフトウェアで問題解決をしようとしている対象(:ドメイン)をモデリングすることでソフトウェアの価値を高めようとする。

モデルとは

問題解決のために、物事の特定の側面を抽象化したもの。

  • ドメインモデル(モデル):ドメインの問題を解決するためのモデル
  • データモデル:データの永続化方法を決めるためのモデル

モデルの例

モデルは、特定の事象を抽象化したもの。
例えば、履歴書の場合、以下の情報が読み取れる

  • 名前
  • 経歴
  • 志望理由
  • 顔写真
  • 筆跡、筆圧
  • 履歴書のメーカー
  • 履歴書を書いた時の気持ち

これらの情報から、どのような情報をソフトウェアに取り込むかを決める過程が「抽象化」。
抽象化によってできた成果物が「モデル」。

良いモデル、良くないモデル

良いモデル

良いモデルとは、問題を解決できるモデル
理解がしやすい、きれいに見えるなどは二の次。あくまでモデルは、問題解決のために、事象を抽象化したものである。

良くないモデル

モデルのステータスが追加できない、発生するイレギュラーケースに対応できないなど、実務での問題を解決できないモデルは、いかにテストがそろっていようと、バグが少なくても、評価されない。

良いモデルを作るには

ドメインエキスパートと会話をする

そのドメインに詳しい人(:ドメインエキスパート)と会話をし、モデルに知識を反映することが大事。
ただし、ドメインエキスパートは開発のエキスパートではないため、次の視点も必要になる。

運用して得られた発見をモデルに還元する

運用して発見した改善点は、細かく追加していく。
モデルは最初から完成せず、徐々に改善していくもの。
(そのため、コードは頻繁な変更に耐えられる作りにする必要がある。)

DDDの問題解決のアプローチ

以下のアプローチを行うことが重要。

  • モデルを継続的に改善
  • モデルを継続的にソフトウェアに反映

モデルの継続的な改善

ドメインについての理解を深め、モデルを継続的に改善する。

  • ユビキタス言語
    • 発見したモデルに関する物事を、プロダクトに関わる全ての場所において同じ言葉で表現すること
    • ビジネスサイドの人とも共有する
    • 会話でも、ドキュメントでも、コードでも、同じ言葉を使う
  • 境界づけられたコンテキスト
    • あるモデルを同じ意味で使い続ける範囲のこと
    • 第3章参照のこと

モデルを継続的にソフトウェアに反映

  • モデルを直接表現するコードへの反映
    • 極力モデルとコードを近づける
  • モデル表現を隔離するアーキテクチャ
    • モデルを表現するコード意外にもUIやDBとのやりとりを表現するコードが存在する
    • モデル表現と、それ以外を表現するコードが混ざるとややこしい
    • モデルの表現を行うコードを「ドメイン層」として隔離する

取り組む上で重要な考え方

  • 課題ドリブン
    • 意思決定の際には、課題を明確にして解決できるかを考える
    • ルールより課題解決ができるかを重視する
  • 小さく初めて、小さく失敗する
    • モデリングに1週間かけるより小さく実装する方が良い
    • 小さい部分から始めて、少しずつ成功体験を積み上げる

第2章 モデリングから実装まで

ドメインモデリング

DDDでは、モデリングの方法や、アウトプット形式が迷うポイント。
今回は比較的シンプルで、小さく始めて効果を出しやすいユースケース図とドメインモデル図を利用する。

ユースケース図

ユースケース図は、「ユーザの要求に対するシステムの振る舞いを定義する図」。
ユーザを定義し、棒人間で表して、ユーザが行う動作を吹き出しで「〇〇を××する」といった形式で書き出す。
さらに、今回のモデリングにおけるスコープも、図中に明記する。

ユースケースを書き出すことで、どのようなモデルを作れば良いか判断できるようにする。
ユースケースが具体化されていないと、モデルで解決すべき問題点が明確にならず、良いモデルとそうでないモデルの区別が付かなくなってしまう。

また、モデリングにおけるスコープを明記することで、議論の発散を防ぐ。

ドメインモデル図

ドメインモデル図は、簡略化したクラス図。

  • 書くこと
    • オブジェクトの代表的な属性
    • 「ルール」、「制約(ドメイン知識)」を示した吹き出し
      • 箇条書きなど
      • 状態遷移図などでもOK
    • オブジェクト同士の関係
      • 集約内の参照は「-◆」で表す(インスタンス参照)
      • 集約外の参照は「→」で表す(ID参照)
    • 多重度の定義
    • 集約範囲の定義
    • 具体例などもあれば
  • 書かないこと
    • メソッド

(ここは、書籍のP24にある図を見た方がわかりやすい。)

ドメインモデルの実装

作成したドメインモデルを、コードに落とす。

ドメインモデル貧血症

ドメインモデルを実装するためのオブジェクトでありながら、ドメイン知識をほぼ持っていないオブジェクトのこと。
書籍ではドメイン貧血症のコードをリファクタリングする。

アーキテクチャ

まず、全体のアーキテクチャを決定する。
レイヤーを定義して、各レイヤーに実装するクラスの方針を定義する。
(ここでは、オニオンアーキテクチャを採用)
詳しくは第5章参照。

ドメイン貧血症のコード

(このメモを書いている私の都合でPHPで書きます...)

❌ドメイン貧血症のコード
public class Task
{
   private id;
   private taskStatus
   private name;
   private duDate;
   private postponeCount;

   // 全ての属性には、publicなsetterとgetterが存在する
}
  • 問題点1:不整合なデータをいくらでも作り出せる
    • 全てのsetterがpublicであるため、自由にデータを作り出せてしまう
  • 仕様の把握のためには、多くのクラスやコードを辿る必要がある
    • ステータスや期日変更にはどんなパターンがあるかこのクラスだけではわからない

ドメインモデル知識を表現した実装

ドメイン貧血症を防ぐためには、以下に注意する

  • ドメイン知識はドメイン層で実装する
    • ユースケース層で実装しない
    • ユースケースには「なにをしたいか(What)」のみが記載されるようにする
  • 想定外の操作が行われないようにする(例えばpublicなsetterは作成しない)

ドメイン層オブジェクト設計の基本方針

以下の2点に従う。

  • ドメインモデルの知識を対応するオブジェクトに書く
  • 常に正しいインスタンスしか存在させない
    • 生成条件を強制する(なにもないコンストラクタは実装しない)
    • 内部状態の変更を強制する(ミューテーションを起こすメソッドを正しく実装し公開する)

第3章 固有のモデリング手法

集約

集約とは、「必ず守りたい強い整合性を持ったオブジェクトのまとまり」のこと。
以下の2ルールにしたがって定義する。

  • 強い整合性確保が必要なものを集約にする
    • オブジェクトの属性が他のオブジェクトに強く影響する場合など
  • トランザクションを必ず1つの集約にする
    • 整合性を守るため、集約単位でリポジトリからデータを取り出したり、更新したりする

集約ルートとは

各集約には、ルートとなるオブジェクトを1つ決め、集約ルートと呼ぶ。

集約の決め方

  • 集約の境界は、機械的な決め方は存在しない
  • 整合性確保の重要性を加味して決める
    • 複数集合間で整合性を確保しないわけではないので、整合性確保が必要だから必ず集合にする必要はない
  • トランザクション範囲の適切さも加味する
    • 大きすぎる集合だと、DBへ反映するときに負荷がかかる

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

同じシステム内でも、1つの事象をすべて1モデルで表すのには無理がある。
そのため、適切な範囲でモデルがカバーする範囲を分割する。
このモデルがカバーする範囲のことを「コンテキスト」と呼ぶ。
(モデルを別にして、コンテキストが変わった時は情報を詰め替えるイメージ??)

第4章 設計の基本原則

凝縮度

凝縮度とは、1クラスでの「責務、データ、振る舞いの関連の強さ」の尺度。
なんでもできるクラスを作るのではなく、責務に沿った内容が集約されているかどうか。
一般的に高凝縮がいいとされる。

責務を明確にするため、「このクラスは何をするクラスか」を明確にすべき。

  • ❌ 低凝縮に陥る例
    • クラス名自体が責務を曖昧にしている
    • クラス名と関連が明確でないデータ名
    • 目的語が欠如したメソッド名(「なにを」インクリメントするのか、など)
    • 保持しているデータと関係ないロジック

結合度

結合度とは、複数のクラス同士が依存している度合い。
一般的に、低結合が良いとされている。
結合度が高いと、依存先クラスの修正による影響を受けやすくなる。
また、実現したい処理を、多くのクラスを組み合わせないと実現できない場合は、すでに結合度が高くなっていると考えられる。

高凝縮・低結合で得られるメリット

  • コードを理解しやすくなる

  • コードの修正/拡張がやりやすくなる

  • バグが含まれにくくなる

  • コードを再利用しやすくなる

  • テストがしやすくなる

第5章 アーキテクチャ

DDDで提唱されているアーキテクチャの解説。

3層アーキテクチャ

概要

3層アーキテクチャ

デメリット

以下の理由で可読性や保守性が下がる。

  • ビジネスロジックが膨らむ。(低凝縮になる)
    • ビジネスロジックがユースケースとドメイン知識両方を担っているため
  • ビジネスロジック層とデータアクセス層が高結合になってしまう
    • ドメイン知識がビジネスロジック層とデータアクセス層にあるため

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

概要

3層アーキテクチャのビジネスロジック層を2つに分けたもの。

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

メリット

  • レイヤーごとに責務がはっきりし、可読性、保守性が上がる(高凝縮になる)。

デメリット

  • ドメイン層がインフラ層に依存してしまう
    • ドメイン層にリポジトリをおく場合、DBやORマッパーに依存した実装になる

モデルを継続的に改善するためには、ドメイン層はインフラ技術に依存すべきではない。

オニオンアーキテクチャ

概要

レイヤードアーキテクチャから、ドメイン層とインフラ層の依存関係を逆転させたもの。
図中の白矢印は、インターフェイスの実装を示す。

オニオンアーキテクチャ

名前の通り、丸型で表すこともできる。
この場合、依存関係は「外側から内側」のみ許される。
オニオンアーキテクチャ

プレゼンテーション層、インフラ層、テストは、それぞれアダプターを通して外部と通信する。
(スマホアプリ、ブラウザ、RDB、ファイル、外部サービス、結合テスト など)

  • ドメイン層
    • ドメイン知識の表現
    • ドメイン層の独立で他の層への依存を持たせない
    • 整合性が保てるメソッドのみ外部に公開する
    • 実装するクラスは以下の通り
      • エンティティ
      • 値オブジェクト
      • ドメインイベント
      • リポジトリインターフェイス
      • ドメインサービスファクトリー
  • ユースケース層
    • ドメイン層のメソッドを使ってユースケースを実現する
    • 特定のクライアントに依存させない (クライアントに合わせるのはプレゼンテーション層)
    • 実装するクラスは以下の通り
      • ユースケースクラス
      • プレゼンテーション層との入出力定義クラス
  • プレゼンテーション層
    • アプリケーション外部との入出力を実現
    • 実装するクラスは以下の通り
      • コントローラ
      • アプリケーション外部との入出力を実現するクラス
  • インフラ層
    • 下位レイヤーのインターフェイスを実装する
    • 実装するクラスは以下の通り
      • リポジトリ実装クラス

メリット

  • ドメイン層を特定の技術に依存させずに済む

ヘキサゴナルアーキテクチャ

概要

アプリケーションが外の世界と通信する際には、専用のポートとアダプターを作成して通信させるアーキテクチャ。
レイヤーの分け方が異なるが、基本的にはオニオンアーキテクチャの思想を踏襲したもの。
(別に六角形である意味はない。)

クリーンアーキテクチャ

概要

オニオンやヘキサゴナル、その他のアーキテクチャを受けて概念を結合しようとしたアーキテクチャ。

なお、筆者のおすすめはシンプルさと元々のDDDが目指す思想との差異から「オニオンアーキテクチャ」だそう。

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

  • 1アプリケーション1コンテキストとするのが簡単
    • コンテキストごとのマイクロサービスとなる
    • ただし実装コストは高くなる
  • パッケージで区切ってしまう
    • 逆に複雑になる時もある
    • パッケージで区切る方法で始めて、後にサービスを分割する方法もアリ

後半はこちら

8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?