MENU

「ドメイン駆動設計入門」まとめ

目次

はじめに

書籍のまとめです。

会社の輪読会で一通り読んだのでまとめてみました。

ドメイン駆動設計とは

  • ドメイン知識をコードに埋め込むための設計手法
  • ドメインとは「プログラムが適用する対象となる領域」
  • ドメインモデルとは「ドメインの概念を抽象化したもの」
  • ドメインオブジェクトとは「ドメインモデルをソフトウェアで動作するモジュールとして表現したもの」
  • ドメインモデルを介してドメインとドメインオブジェクトが相互に影響し合う開発行う

値オブジェクト

  • プリミティブ値よりも具体的な「システム固有の値を表現するために定義されたオブジェクト」のこと
  • 値の性質をもつ
    • 不変である
    • 交換が可能である
    • 透過性によって比較される
  • どこまで値オブジェクトにするかは「そこにルールが存在しているか」「それを単体で取り扱いたいか」の2つを満たしているかを判断材料とする
  • 値オブジェクトの効果
    • 表現力がます
    • 不正な値を存在させない
    • 誤った代入を防ぐ
    • ロジックの産卵を防ぐ

エンティティ

  • エンティティは、その属性が変更されても、同一性(identity)によって識別される
  • エンティティの性質
    • 可変である
    • 同じ属性であっても区別される
    • 同一性を持つ
  • エンティティはライフサイクルが存在し、連続性が存在する
  • ドメインオブジェクトを定義するメリット
    • コードのドキュメント性が高まる
    • ドメインの変更をコードに伝えやすくする

ドメインサービス

  • 値オブジェクトやエンティティに記述すると不自然になる振る舞いを担う
    • 複数のドメインオブジェクトを横断するような操作がよくある
  • ただし、ドメインサービスに処理を集めすぎると、ドメインモデル貧血症になる

リポジトリ

  • データの永続化・再現のみを担うレイヤー
  • 特定の技術基盤に流用するために、ドメインオブジェクトにゲッターやセッターを定義するようなことは避けるべき
  • ビジネスロジックをコード上で実現する人は、どう永続化するかは考えなくていいし、どう永続化・再現するかを考える人は、ビジネスロジックのことを考えなくてよくなる

アプリケーションサービス

  • これまで出てきたドメインオブジェクトを組み合わせてユースケースを実現する
    • エンティティ
    • 値オブジェクト
    • ドメインサービス
    • リポジトリ
  • アプケーションサービス以外がこれらを直接操作してしまうと、アプリケーションサービスの意義が薄くなる
  • アプリケーションサービスの帰り値にはDTOを使おう(Clientでドメインロジックを操作できてしまうことになるため
  • コマンドオブジェクト
    • 入力データのカプセル化を行い引数の散乱を防げる
  • 凝集度
    • ざっくり理解:むやみやたらに DI せず、必要なコードは必要な場所で使うようにしようね。全てのインスタンス変数は全てのメソッドで使われるべき
    • しっかり理解:LCOM

依存関係のコントロール

  • 2種類の依存
    • 参照による依存
    • 汎化による依存(I/Fの実態を実装)
Service LocatorコンストラクタインジェクションIoCコンテナ (DIコンテナ)
依存の取得方法呼び出し側が locator.Get(“X”) で探しに行くコンストラクタ引数で明示的に渡すコンテナが自動で依存を解決して渡す
依存の見え方ソースコードからは依存が隠れる(どこで使っているか分かりにくい)コンストラクタのシグネチャで依存が明示されるコンストラクタやフィールドに依存が明示される(自動解決なのでコードに直接は出ない)
結合度Locatorに強く依存する(隠れた結合)疎結合(最もシンプルで明示的)コンテナに依存するが、注入対象は疎結合
テストのしやすさモック差し替えが面倒(Locatorに登録し直す必要あり)モックをコンストラクタ引数で渡すだけなので簡単モックをコンテナに登録すれば差し替え可能
依存解決のタイミング実行時(サービス取得時に実行時エラー)コンパイル時に依存が明示され、生成時に解決(コンパイルエラー)アプリ起動時に依存グラフを解決(アプリ起動時に実行時エラー)
規模感アンチパターンなので推奨しない小〜中規模であれば。大規模だと依存解決が手作業で煩雑になることも大規模な依存関係を自動で解決できる
  • DDDで開発するような規模感の開発ならDIコンテナを使おう

ファクトリ

  • 複雑なオブジェクト(道具)はその生成過程も複雑になる
  • この生成を責務とするオブジェクト
  • 複雑かどうかの判断は、「コンストラクタ内で他のオブジェクトを生成するかどうか」を指標にすると良い
  • ファクトリ経由での生成を強制できない
  • 本章では採番処理を例に各実装方法の比較がされていたがULIDが良さそう

データ整合性

  • DBのトランザクション設定
    • ダーティリード(Dirty Read):
      • 未確定のデータを読み込んでしまう問題。もし元のトランザクションがロールバックされると、読み込んだデータは存在しないことになります。
    • ノンリピータブルリード(Non-repeatable Read):
      • トランザクション内で同じデータを複数回読み込んだ際に、他のトランザクションのコミットによって値が変わってしまう問題。
    • ファントムリード(Phantom Read):
      • トランザクション内で特定の条件でデータを複数回検索した際に、他のトランザクションのコミットによって、新しい行が増えたり、既存の行が削除されたりする問題。
  • Unit of Work(アプリケーション層でトランザクション処理を管理するデザインパターン)
特徴DB トランザクションを直接使うUOW パターンを使う
関心の分離低い高い
テストの容易さ低い(DB 接続が必要)高い(モック可能)
柔軟性低い高い
コードの保守性低い(複雑なシステムでは)高い
初期実装の複雑さ低い高い
  • 整合性の担保は色々方法があり、プロジェクトごとに適した方法を選択しよう

集約

  • 集約とは「一緒に守りたいルール(不変条件)を持ったオブジェクトのまとまり」で、変更の単位になる。
classDiagram
    class Order {
        +orderId
        +status
        +totalAmount
        +addItem(productId, quantity)
        +cancel()
    }

    class OrderItem {
        +orderItemId
        +productId
        +quantity
        +price
    }

    Order "1" o-- "*" OrderItem : 集約の中の部品
  • 集約には「代表(入口)」となる集約ルートが 1 つだけ存在する。
    • 外部のコードは、そのルート経由でだけ内部のオブジェクトを操作する。
  • デメテルの法則により、「友達の友達…」と奥まで直接触らず、ルートにお願いする形で操作する。
  • 集約の区切り方は「一緒に変更したいもののまとまり(変更の単位)」で考えるのが基本。
    • 逆に「別々に変えてよいもの」は別の集約に分ける。
  • 集約はなるべく小さくし、1 集約で 1 トランザクションが完結するイメージを目指す。

仕様

  • あるオブジェクトがある評価基準に達しているかを判定するオブジェクトである
  • サークルが満員かどうかを判定する例が取り上げられている
  • 仕様はドメインオブジェクトなので、リポジトリの利用は避け代わりにファーストクラスコレクションを利用する
  • ファーストコレクションとは、リポジトリで取得したユーザーの一覧をあらかじめ全部保持しておくようなオブジェクト

アーキテクチャ

  • クリーンアーキテクチャ
    • 一番中心のEntitiesはドメイン駆動設計のエンティティというよりドメインオブジェクトに近い概念
    • ヘキサゴナルアーキテクチャと違う点は実装方法に言及している点(インターフェースを使って疎結合にしよう)

ドメイン駆動設計

  • パターンだけ取り入れたDDDを軽量DDDと呼ぶ。
  • パターンに固執せず、ドメインの本質と向き合おう
  • プロジェクトにおける共通言語(ユキビタス言語)を決めましょう
  • コンテキストが変わればユキビタス言語も変わる(境界づけられたコンテキスト)
  • コンテキストマップでコンテキスト同士の関係を定義しドメイン全体を俯瞰できるようになる
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次