where句の再利用

ConditionBeanにおけるwhere句の再利用テクニックについて説明をします。

アプリケーションごとに独自のポリシーを決めることがお奨めですので、アーキテクトの方も必ずこのページをご覧下さい。プロジェクトで統一的なやり方で横展開 することが重要です。

where句を再利用しよう

まとまった条件があったら

例えば...

  • 'S'で始まる会員
  • とある商品を購入
  • 正式会員

を、"特別過ぎる優待会員" として扱うというような業務仕様があったとして...

e.g. ある特別な商品を購入したことのある会員名称が "S" で始まる正式会員を検索 @Java
MemberCB cb = new MemberCB();
cb.query().setMemberName_PrefixSearch("S");
cb.query().setMemberStatusCode_Equal_Formalized();
cb.query().existsPurchaseList(purchaseCB -> {
    purchaseCB.query().setProductId_Equal(SPECIAL_PRODUCT_ID);
});

まあ、こんな感じの実装になります。

そして、その条件を複数箇所で使うとなったら...べたべたコピペ...あっ!? "特別過ぎる優待会員" の条件が変更されたら?ひー

誰か一人が実装して、それをみんなが再利用 するのが一番です。

まるごと一緒なら...まあいいけど

もし、この特別過ぎる優待会員の条件を利用する複数箇所において、取得したいテーブル(select句)やソート順までが全く同じ仕様になる、 つまり、検索まるごと再利用できるのであれば、Behaviorの "Exクラス" に独自のメソッドを作って再利用するやり方でも問題ありません。

現場フィット - ドメインロジックの再利用

取得したいテーブルやソートは違うもの

しかし、業務仕様はなかなかそういうものではなく、プロセス毎に "特別過ぎる優待会員" のソートの仕方が違ったり、一緒に取得したい関連テーブルが違ったり、そもそも検索の基点テーブルが違ったり(例えば、"特別過ぎる優待会員" の購入リストを検索するとか)と、"特別過ぎる優待会員" という検索条件は様々な場面で様々な利用方法が考えられます。つまり、再利用したいポイントはwhere句の一部条件 なのです。

ConditionQueryのExクラスにて!

そのような場合は、ConditionQuery の "Exクラス" にまとまった条件を設定するメソッド を作って再利用するのが一番です。そのようにしておくことで、DB変更などで特別過ぎる優待会員の条件が変わった場合も修正は一箇所で済みます。

再利用メソッド (ArrangeQuery)

メソッドは arrange !?

メソッド名は arrange で始まるのが習慣としてお奨めです。この再利用メソッドを定義する方法、もしくは、そのメソッド自体を ArrangeQuery と呼んでいます。

少なくとも再利用メソッドの頭文字はアプリケーション全体で合わせておくと良いでしょう。 再利用メソッドは、いかにディベロッパーに使ってもらうか(存在に気付いてもらうか)がキーポイントです。 query()と書いた後とりあえず "a" で補完すればその CQ の再利用メソッド(の一覧)が補完される という状態にしておけば、ディベロッパーが再利用メソッドを探す手間が無くなります(補完されなければ何もないということで)。 さらには JavaDoc がしっかり書かれていれば、ディベロッパーは補完するだけで自分が必要なメソッドかどうかが判断できます。

e.g. ある特別な商品を購入したことのある会員名称が "S" で始まる正式会員という条件を設定するメソッド @Java
public class MemberCQ extends BsMemberCQ {

    ...

    /**
     * 特別過ぎる優待会員
     */
    public void arrangeTooPreferentialMember() {
        setMemberName_PrefixSearch("S");
        setMemberStatusCode_Equal_Formalized();
        existsPurchaseList(purCB -> {
            purCB.query().setProductId_Equal(SPECIAL_PRODUCT_ID);
        });
    }
}

再利用メソッドの利用

再利用メソッドが定義されていれば、後はディベロッパーはConditionBean実装時にそのメソッドを呼び出すだけです。 プログラム上は業務的な目的の指定だけとなり、業務仕様への依存性が減ります。

e.g. 特別過ぎる優待会員を検索 @Java
MemberCB cb = new MemberCB();
cb.query().arrangeTooPreferentialMember(); 
cb.query().addOrderBy_...();
... = memberBhv.selectList(cb);

(ConditionBeanでなく)ConditionQueryのExクラスに定義することで関連テーブルでも利用可能です。

e.g. 特別過ぎる優待会員の購入リストを検索 @Java
PurchaseCB cb = new PurchaseCB();
cb.query().queryMember().arrangeTooPreferentialMember();
cb.query().addOrderBy_...();
... = purchaseBhv.selectList(cb);

order by 句も再利用

同じ仕組みで order by 句も利用することは可能です。

OrScoprQuery や ColumnQuery は?

OrScoprQuery や ColumnQuery は、ConditionBeanドリブンのメソッドであり、ConditionQuery からは利用できません。なので、ConditionQueryではなく ConditionBean のExクラスで再利用メソッドを定義する必要があります。 (ちょっと残念ですが、ここはAPI設計がうまくいきませんでした)

その条件、ひとことで言うと?

えーっと...

  • 'S'で始まる会員
  • とある商品を購入
  • 正式会員

これをひとことで言うと?.........特別過ぎる優待会員!

業務上の目的を導き出すこと、これが再利用の始まりです。

その目的を実現するための手段は、後に変わるかもしれません。他の人も使いたいかもしれません。 各プログラムは、できるだけ業務上の目的だけを意識して、手段は一元管理。これは ArrangeQuery だけでなく、プログラミングにおける再利用の基本となります。

いかに、抽象度を一つあげた業務概念を見つけるか?...常に目を光らせておきましょう。

CBインスタンスは再利用しない!?

プロセスごと (画面ごと) に欲しいデータ量が違います。 そこが完全一致することはあまりないと。 どういう風に絞り込むか?は再利用できることが多い一方で、何を取得するか?は一概に共通化できない。 ゆえに、SetupSelectは(あまり)再利用するべきではないとjfluteは考えています。

そもそも、DBFluteのポリシーとして、なんでもかんでも取得するのではなく、また、LazyLoadするのではなく、"何を取得するか?" をプログラム上でベタベタに明確にすることが大切だという考えがあります。それゆえに、SetupSelectが用意されています。

極端な話...

  1. new MemberCB()はそれぞれの画面で (newは再利用しない)
  2. それぞれの画面でsetupSelect (データの取得は画面ごとに)
  3. query()の絞り込み条件だけ、arrangeメソッドなどで再利用
e.g. setupSelectはそれぞれ、query()のarrangeで再利用 @Java

// 画面側のメソッドにて new
MemberCB cb = new MemberCB();

// SetupSelectはそれぞれで欲しいもの書く
cb.setupSelect_MemberStatus();
cb.setupSelect_MemberServiceAsOne();

// 絞り込みは必要に応じて小分けに再利用
cb.query().arrangeTooPreferentialMember(); 
cb.query().existsPurchaseList(...)

// OrderByもわりと再利用しづらいもの
cb.query().addOrderBy_...();

... = memberBhv.selectList(cb);

という感じ。

通常、newする部分は一箇所にまとめるのが再利用の鉄則ではありますが、こういった業務的なクラスに関しては例外です。 挙動の一括変更は、Exクラスやdfpropでいろいろできるので、その部分のデメリットはほとんどありません。

検索まるごと再利用したいケースももちろんありますが、そこまでに数は多くないはずだと。 それよりも、A画面ではSetupSelectを16個、B画面ではSetupSelect2個、でもA画面で使ってる検索をB画面でも使うというような現象をあまり発生させたくないと。 動くは動きますが、「表示項目のわりにはちょっともったりした画面」になる可能性があります。 可読性的にも、何を取得しているのか?っていうのが明確な方が、画面のView側やコントローラー側の実装がやりやすいので、あまり隠蔽し過ぎない方がいいと考えます。

なので、BehaviorのExクラスよりも、ConditionBean, ConditionQueryのExクラスの方と親友になってほしいと思っています。

引数リモコンパターンはやめたい

こういうのです。

e.g. やって欲しくないやり方 @Java

// 画面側のメソッド
public void index() {
    // これもう、なに検索してるの?
    ... = logic.selectNandemoMember(null, "S", null
            , false, true, false, CDef.MemberStatus.Formalized
            , true, false, true, true);
}

...

// ロジック側の再利用メソッド
public List<Member> selectNandemoMember(Integer memberId
                                      , String memberName
                                      , Date birthdateFrom
                                      , boolean hasFormalizedDate
                                      , boolean existsPurchase
                                      , boolean existsLogin
                                      , CDef.MemberStatus statusCode
                                      , boolean setupSelectService
                                      , boolean loadPurchase
                                      , boolean loadLogin
                                      , boolean exceptMobileLogin) {
    ...
}

これは既に、DBFluteをラップした独自ライブラリと言えるでしょう。 ConditionBeanの良さを完全に打ち消してしまっています。 引数リモコンパターン と呼んでいます。

こういったメソッドは、すべて一人で作り上げるのではなく、複数人が関わって徐々に育てられていきます。 引数がちょっとずつ増えていって...最初に作った人もこんな大きなものになるとは思っていなくて、もっと小さな便利メソッドのつもりだったはず。

一度こうなっていくと、あとの人は "このメソッド使って検索しないといけないのかなぁ" と思い、Conditionbeanではなく...引数をリモコンとしたDBアクセスライブラリを習得し、一生懸命自分の要件を満たす組み合わせを考えます。 足りなければ引数を増やすか、引数の増えた新たなメソッドを追加するか...気付いたら、ConditionBeanを書いてないという状況に...

難しいのは、ある程度は Good です。jfluteもある程度はこういうのも作ることはあります。 ですが、引数でほんのちょっとだけ挙動を変える。"ほんのちょっと" と "引数リモコン" の境目が曖昧です。先の通り、最初の実装者の意図しない方向に成長する可能性があります。

やはり、基本は "new CB" はそれぞれの画面のプログラム上で書く、というポリシーがわかりやすいのかなと。 丸ごと再利用する検索を作るなら作るで、わかりやすさをキープするために丁寧に取り扱いましょう。

ブログにも書きました。

Exampleのススメ

dbflute-basic-example では、実際に ArrangeQuery を定義し、テストケースで利用しています。

DBFlute Example - DIコンテナ