Query Object
1. 目的
- 検索条件をひとつのオブジェクトにカプセル化することで、動的な検索条件の組み立てを柔軟かつ安全に行いたい
2. 課題
動的な検索条件を素朴に実装すると問題が多発する
- 条件が動的に変わる検索処理を素朴に実装した場合発生する問題:
- 条件の組み合わせごとにメソッドを増やすと、Repository が爆発的に肥大化する(
findByAAndBAndC...)
- 条件の有無を
null チェックで分岐する処理が各所に散在する
- ページング・ソートを同時に扱うと引数が増えすぎる
- 検索条件をレイヤー間で渡しにくい(引数バケツリレー)
3. 解決策
Criteria オブジェクトの導入
- 検索条件/ページング/ソートを Criteria オブジェクト1つ にまとめ、
find(criteria) に渡す
4. メリット
Repository のインタフェースが安定する
- 条件が増えても
find(criteria) のシグネチャを変えなくてよい
動的条件の組み立てが自然に書ける
- 未入力項目のスキップなどを Criteria 内に閉じられる
ページング・ソートを一元管理できる
権限による絞り込みをレイヤーで分離できる
テストがしやすい
- Criteria を差し替えるだけでさまざまなケースを検証できる
URL パラメータとの親和性が高い
- POJO なのでシリアライズ・デシリアライズが容易
- その他の言語でもシンプルなハッシュマップで再現できる
5. デメリット
シンプルな検索には過剰
- 条件が固定・少ない場合はクエリメソッドの方が明快
- ユーザー向けの画面は条件が固定/少ない場合が多く、その場合は過剰実装になりがち
Criteria が肥大化しやすい
- 機能追加を重ねると何でも入ったオブジェクトになりがち
学習コストがある
- Specification や CriteriaBuilder などフレームワーク固有の API の理解が必要になる場合がある
- フレームワークを利用しない場合もそれなりに複雑なロジックになる
複雑な JOIN には限界がある
- 多テーブルにまたがる集計などはネイティブクエリや QueryDSL との併用が必要になる
- そもそもそのようなクエリをオンラインで実行するべきかという観点もある
6. 注意事項
適用範囲を絞る
- 管理ツール・検索特化画面など動的条件が多い場所に限定し、一般ユーザー向けの固定画面には使わない
Criteria の肥大化を防ぐ
- 機能ごとに Criteria を分割し、1クラスに詰め込みすぎない
ユーザーが自由にクエリを組み立てるのとは別物
- 検索項目は設計者が固定し、条件の与え方をユーザーに委ねるに留める
パフォーマンスを意識する
- 動的条件でもインデックスが効くかどうかを意識して設計する
段階的に導入する
- 最初からCriteriaを設計するのではなく、クエリメソッドが限界を迎えてから移行するのが健全
参考資料
- Patterns of Enterprise Application Architecture(2002, Martin Fowler) -- Query Object パターンを解説、原典的存在