Lazy Load
An object that doesn’t contain all of the data you need but knows how to get it.
-
DBからメモリ上のオブジェクトにデータをロードする時、関連するオブジェクトも一緒にロードするように設計すると便利
-
クライアントコードで明示的にロードしなくて済む
- 【補】サービス層とか
-
- 芋づる式にオブジェクトグラフ全部ロードするのはパフォーマンスに悪影響
-
そこで読み込みを遅延させる
- 読み込む必要がなかったとき得する
How It Works
種類
-
主要な実装は4つある
- Lazy Initialization
- Virtual Proxy
- Value Holder
- Ghost
Lazy Initialization
- Kent Beckのパターン本に書いてあるらしい?
-
get時、フィールドがnullかどうかチェックする
- nullなら算出してセットしてから返す
-
nullが有意な値の場合は、Special Caseを適用せよ
-
【補】UnknownのnullとNot Applicableのnullがある
- 後者には例えばNull Object Pattern等を適用せよ、ってこと
-
-
DBのスキーマに依存しやすい
- Active Record, Table Data Gateway, Row Data Gatewayではうまくいく
-
Data Mapper採用時はうまくいかない
- MapperでドメインモデルとDBスキーマとを切り離しているのに台無し
Virtual Proxy
- Data Mapper採用時はLazy Initializationがうまくいかないので、この間接層を使う
- ロード対象のオブジェクトをVirtual Proxyでくるむ
- get時はVirtual Proxyを返す
- Virtual Proxyのプロパティの初回アクセス時に読み込みを行う
-
利点
-
ロード対象のオブジェクトそのものを返しているように見える
- 読み込み処理がVirtual Proxyの中でカプセル化されている
- Domain ModelはLazy Loadの存在を知らない
-
-
欠点
- 組み込みの等価判定でコケる
- 静的型付け言語では、Lazy Load対象のオブジェクトのクラスの数だけVirtual Proxyを作らなければならない
-
コレクションに対してはこれらの欠点はあてはまらない
-
組み込みの等価判定でコケる
- コレクションは元々そう
-
クラスの数だけVirtual Proxyを作らなければならない
- コレクションに対して1つ作れば良い
-
Value Holder
- Virtual Proxyのラップ対象をObjectに一般化した感じのもの
Ghost
-
どういうやつ
- 一度に全フィールドをLazy LoadするLazy Initialization
- 自分自身のVirtual Proxy
-
ロードステートを持たせる
GHOST
なら、プロパティアクセス時にロードを行うLOADED
なら、ロード済のフィールドを返す
-
ripple loading
- 余計なDBアクセスが生じてしまう
- 【補】N+1問題みたいなイメージ
-
Lazy Loadのコレクションを持つのではなく、コレクション自体をLazy Loadせよ
-
コレクションが巨大だと適用不可能
- 「全世界のIPアドレスのコレクション」とか
- が、普通そんなケースはない
-
無くはない
- その場合はValue List Handlerを適用せよ
-
-
AOPを適用してgetterにLazy Load処理を差し挟むとよい
- Domain Objectから切り離して透過的にできる
-
ユースケースにより異なるlazinessが必要なケースではどうする?
-
Data Mapper採用時は、lazinessごとにData Mapperを分ける
- Eager LoadするMapper
- Lazy LoadするMapper
- アプリケーションコード(サービス層とか)側で注入し分ける
- 何種類も作る必要は普通ない
-
When to Use It
-
DBから取得するデータの量と、アクセス回数による
-
読み込み量を減らすよりは回数を減らせ
- Serialized LOB等、データが大きい場合でさえも、余計なカラムを読み込むコストは大したこと無い
-
- 複雑性を増すため、必要のない場合は使わないのが良い
Example: Lazy Initialization
class Supplier...
public List getProducts() {
if (products == null) products = Product.findForSupplier(getId());
return products;
}
Example: Virutal Proxy
- 本物のクラスに見えるプロキシで包むのがキモ
- pp.204-205のコードを一部改変・コメント付した
class Supplier...
/** ProductsのリストのVirtual Proxy */
private VirtualList products;
/**
* コレクション読み込みインタフェース
* 具体的に何のクラスのオブジェクトをどのMapperで読み込むかは実装クラスで決める
*/
public interface VirutalListLoader {
List load();
}
/**
* Productのコレクションを読み込むクラス
* ProductMapperに読み込みを委譲
*/
public static class ProductLoader implements VirtualListLoader {
private Long id;
public ProductLoader(Long id) {
this.id = id;
}
public list Load() {
return ProductMapper.create().findForSupplier(id);
}
}
/**
* SupplierのMapper
* Supplier構築時にProductのコレクションのLazy Loadも仕込む
*/
class SupplierMapper...
protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {
String nameArg = rs.getString(2);
Supplier result = new Supplier(id, nameArg);
result.setProduct(new VirtualList(new ProductLoader(id)));
return result;
}
/**
* ListのVirtual Proxy
*/
class VirtualList...
{
/** コレクション実体 */
private List source;
/** データ読み込みクラスをコンストラクタ注入 */
private VirtualListLoader loader;
public VirtualList(VirtualListLoader loader) {
this.loader = loader;
}
/**
* Lazy Load実部
*/
private List getSource() {
if (source == null) source = loader.load();
return source;
}
/**
* Listが持っているメソッドぜんぶ委譲
*/
public int size() {
return getSource().size();
}
public boolean isEmpty() {
return getSOurce().isEmpty();
}
...
}
-
Domain Modelオブジェクトは、単にVirtualListを返せばいい
- Lazy Loadのことを知らない
Example: Using a Value Holder (Java)
-
Virtual Proxyの一般化
- 【補】執筆当時、Javaのジェネリクスは無かったのかな
class Supplier...
private ValueHolder products;
public List getProducts() {
// ValueHolder.getValue()はObjectを返すので
// ここでダウンキャストして返す
return (List) products.getValue();
}
/**
* ObjectのVirtual Proxy
*/
class ValueHolder...
{
/** オブジェクト実体 */
private Object value;
/** データ読み込みクラスをコンストラクタ注入 */
private ValueLoader loader;
public VirtualList(ValueLoader loader) {
this.loader = loader;
}
/**
* Lazy Load実部
*/
private List getValue() {
if (value == null) value = loader.load();
return value;
}
-
Domain ModelはLazy Loadの存在を意識する必要がある
-
getterの中でValueHolderから値を取り出す必要がある
- 汎用のObjectが返るためダウンキャストが必要
-
Example: Using Ghosts (C#)
- Domain ModelのLayer Supertypeに実装すると良い
class Employee
public String Name {
get {
Load();
return _name;
}
set {
Load();
_name = value;
}
}
String _name;
class DomainObject...
protected void Load() {
if (IsGhost)
DataSource.Load(this);
}
-
各getter/setterに
Load()
を書くのがかったるい- AOPを適用すると良い
-
DataSource.Load(DomainObject obj)
で適当なMapperに処理を委譲し、データの読み込み・フィールドのセットを行う- ドメイン層をデータソース層に依存させたくないので、Separated Interfaceを適用する
Mapper.Find(Long id)
では、Ghost Stateのオブジェクトを返す-
Mapper.Load(Object obj)
の中身は?- スカラ型: 単にsetするだけ
-
オブジェクト: 対応するMapperのFindでGhostをsetする
- 再帰的なGhostパターンに成る
-
コレクション: 複雑
- pp.211-214
- 必要なレコードを1クエリで読み込みたい
- ListLoaderにSQLクエリをセットする
英語
-
in one fell swoop
- 一気に