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には…
-
コレクションがない
-
【補】テーブルをリレーションとして使わないならこの限りではない
- 第一正規形:「テーブルがリレーションである」
-
- 継承がない
-
-
両者のスキーマは必ずしも一致しない
- オブジェクト: ビジネスロジックの表現
- 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カラム のような単純なマッピングの場合は冗長
-
更新系
- create, update, delete
- トランザクション境界としてUnit of Workを用いよ
-
オブジェクトグラフの取得について
-
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
- 実装してさんざん悩んだことあるから略