勉強日記

チラ裏

PoEAA ch10 DataMapper

martinfowler.com


Data Mapper

A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.

  • オブジェクトとRDBとのインピーダンスミスマッチ
    • RDBには...
      • コレクションがない
        • 【補】テーブルをリレーションとして使わないならこの限りではない
          • 第一正規形:「テーブルがリレーションである」
      • 継承がない
    • 両者のスキーマは必ずしも一致しない
  • オブジェクトにスキーマ変換の知識は持たせたくない
    • O/Rのスキーマ間のデータのやりとりはそれ自体が複雑
    • RDBスキーマの変更がオブジェクトに影響を与える
  • Data Mapper
    • OとRの間接層
    • DomainObject <-- DataMapper --> RDB
    • 依存を切り離す
      • Domain ObjectはSQLのことを知らない
      • RDBはそれを利用するオブジェクトのことを知らない
      • Domain ObjectもRDBも、Data Mapperのことを知らない
  • Mapperの一種
    • 【補】他にも、DTOのMapperとかがある

How It Works

  • Domain層とData Source層との分離が本層の主要な役目
  • 考慮すべきこと多し
  • O/Rマッピングのロジックが複雑な場合に有効
    • オブジェクトの1フィールド = RDBの1カラム のような単純なマッピングの場合は冗長
  • 更新系
  • オブジェクトグラフの取得について
    • SQLクエリの回数を減らすため、1クエリで複数のオブジェクトのグラフを読み込む
      • 例: PersonにOrderやOrderLineをぶら下げて返す
    • オブジェクトグラフの読み込みは、どこかで打ち切る必要がある
      • さもないと、RDBの中身全部取得することになってしまう
    • 打ち切った先はLazy Load
    • ここにおいて、Domain Modelは「完全にData Mapperを知らない」ことはできない
      • 【補】Domain ObjectにLazy Loadを搭載するということは、何が未ロードになるか知っていなければならない
        • 何が未ロードなのかはData Mapperの知識
  • Data Mapperいくつ作る?
    • 手でコーディングしているならDomain Objectや、そのグラフのrootのクラスにつき1つ
      • 【補】DDD界隈でEntityやAggregateと呼ばれるヤツのことか
    • Metadata Mappingで自動生成しているなら単一でもよい
      • 単一クラスに大量のfindXxxメソッドが生えることになる
  • Identity Maps
    • RDBから読み出したオブジェクトの一意性の維持
      • 【補】1つの主キーに対し、メモリ空間上1つのオブジェクト
    • どこにおく
      • Registryに置く
        • 【補】Singletonみたいなやつ
      • Finderの中に置く

Handling Finders

  • Domain Objectからfindメソッドを呼びたいケース
    • Lazy Loadを適用すれば、Domain Object内での明示的な呼び出しコードは回避可能
      • 【補】associationをたどると暗黙裡にデータ読み込み
    • シンプルなアプリケーションでは大げさ
    • でもDomain ObjectからData Mapperへの依存は避けたい
    • Separated Interfaceを適用せよ
      • Finderインタフェースを切り出す

Mapping Data To Domain Fields

  • Data MapperはDomain Objectのフィールドにアクセスする必要がある
  • でもフィールドをpublicにはしたくない
  • 簡単ではない問題
    • package-private使う?
      • DomainとMapperとを同じパッケージにしてしまうと依存関係が大味になりすぎる
        • Domainに依存するモジュールはMapperにも依存
    • リフレクションで不可視性をバイパスする?
      • 遅い
        • SQLコールに比べれば無視小とも
    • public setter生やす?
      • 工夫は必要
        • 特定のコンテキスト外で呼び出されたら例外送出
        • 特定のコンテキストで使ってほしい旨を名前で表現する
          • 【補】聞いたことのある命名
            • *ForMapper
            • Reconstruct
  • オブジェクトの生成についても同様の問題がある
    • とるべき道は2通り:
      • rich constructor
      • empty object + Lazy Load
    • 著者は前者好み
      • 構築時点でwell-formedなオブジェクトが得られる
      • immutableにできる
        • フィールドをmodifyするpublicメソッドを生やさない
    • rich constructorの問題点
      • 循環参照
        • 構築時に無限再帰に陥る
      • どう回避する
        • rich constructorをやめ、empty object + Lazy Loadにする
          • Identity Mapに登録があればそれを読み出す
          • なければ空のオブジェクトを生成し、即座にIdentity Mapに登録する
    • empty objectの場合、immutableをどう実現するの問題
      • 先述の解決法に同じ
        • 特定のコンテキスト外で呼び出されたら例外送出
        • 特定のコンテキストで使ってほしい旨を名前で表現する
        • リフレクション

Metadata-Based Mappings

  • マッピングの知識どう表現する
    • コードで明示的に書く
    • Metadata Mappingで自動生成する

When to Use It

  • DBのスキーマとオブジェクトモデルとが異なる道を歩むとき
    • 特に、Domain層にDomain Modelを採用した場合
  • ドメインの複雑性が試金石
    • Domain Model + Data Mapper
      • 複雑なドメインロジックに適する
      • 層が余分に必要
    • Active Record
      • ドメインがとても単純で、アプリケーション開発者がDBモデルも触れるならこちらでも
      • Data MapperのふるまいがDomain Object自身に押し込められた形
      • O/Rマッピングが複雑化したら、別層に切り出すほうがよい
  • 自分で作らずに買うという選択肢

Example

  • 実装してさんざん悩んだことあるから略