ルーズなDaoパターンなら見たくない

気にせず突っ込む

Daoパターンというもの自体の説明はしません。厳密なDaoパターンの話じゃないかもしれません。ここでは、jfluteが現場でよく見てきた、

ルーズなDaoパターン

のお話。

【追記】いまなら、Repository層って言った方がわかりやすいかな?

ただなんとなくDBアクセスを隠蔽しようと、Daoレイヤにすべての検索を押し込める

「DBの検索の実装はとにかくこのクラスで」

そこが再利用領域なのかそうではないのか?何のためにそこに実装しようとしているのか?

気にせず突っ込む。

最小公倍数パターン

よくある現象のひとつめ。

A画面: 関連テーブル20個持って来て表示
B画面: 関連テーブル3個持って来て表示

でも、AとBの検索は非常に似ている。
違うのは持ってくるデータの量。

A画面が先に実装されました。
B画面を作る人がその実装を見つけました。

public List<Member> selectMemberList() { // author A
    List<Member> memberList = memberBhv.selectList(cb -> {
        cb.setupSelect_MemberSecurity();
        cb.setupSelect_MemberStatus();
        cb.setupSelect_Sea().withDocksideStage();
        cb.setupSelect_Sea().withHangarStage().withMystic();
        cb.setupSelect_Land().withShowBase().withOneMan();
        cb... // many, many setupSelect
        cb.query().set...
        cb.query().addOrderBy...
    });
    memberBhv.load(loader -> {
        loader.loadPurchase(...);
        loader.loadPiary(...);
        loader... // many, many loadReferrer
    });
}

さて、どうするでしょう?

あっ!
B画面が、そのままA画面の検索メソッドを使ってしまいました。
なぜなら...

Daoレイヤの定義されているわけですから、再利用していいものだと思うでしょう。楽だし。めでたく微妙に重いB画面のできあがりです。

DBFluteであれば、SetupSelect の数ですね。Bから見れば大量の不要な SetupSelect が呼ばれている。

...

C画面: 関連テーブル7個持って来て表示

C画面もA画面の検索を再利用しようとしていますが、ちょっと関連テーブルが足りてないようです。

さて、どうするでしょう?

あっ!
そのメソッドに関連テーブルを追加してしまいました。
なぜなら...

関連テーブルの追加であれば影響はないだろうと考えるでしょう。楽だし。めでたく微妙に重いC画面のできあがりですが、A画面もB画面も微妙に重くなりました。

めでたく最小公倍数のメソッドができました。

public List<Member> selectMemberList() { // author A
    List<Member> memberList = memberBhv.selectList(cb -> {
        cb.setupSelect_MemberSecurity(); // for A
        cb.setupSelect_MemberStatus(); // for A, B
        cb.setupSelect_Sea().withDockside(); // for A, C
        cb.setupSelect_Sea().withHangar().withMystic(); // for A
        cb.setupSelect_Land().withShowBase().withOneMan(); // for A
        cb.setupSelect_Land().withOrleans().withMinio(); // for C
        cb... // many, many setupSelect
        cb.query().set...
        cb.query().addOrderBy...
    });
    memberBhv.load(loader -> {
        loader.loadPurchase(...); // for A
        loader.loadPiary(...); // for A, C
        loader.loadBonvo(...); // for C
        loader... // many, many loadReferrer
    });
}

A画面: 実は要らないテーブルがあった(or要らなくなった)

スピードのためにも、不要な関連テーブルを削りたい。

さて、どうするでしょう?

いやぁ...これはもう削れません。どう影響するのかわからないから、要らなさそうでも削除する勇気はありません。(影響を特定するのが時間がかかるし)

この繰り返し、しばらくすると、実はどの画面も使ってない関連テーブルもできたりします。

でも、削れません。もはや誰がどれを使っているか把握してない。めでたく最小公倍数 "以上" のメソッドができました。

見事なデグレパターン

A画面が「そのメソッドを修正します!」と言い出しました。パフォーマンスチューニングでデータ取得の調整や、絞り込み条件の表現の変更、普通に業務変更による変更など。

A画面からすれば自分で作ったメソッド。BやCから呼ばれていることに気付いているとは限らない。

再利用されることを意識して作っていれば、仕事人としては他の画面からの利用を確認したり、それなりに気をつけるでしょう。

でも、

別に再利用とか気にせず、"DB検索はDaoに実装しないといけない"って言うから、そこに書いただけ

という意識で、Daoメソッドを実装しているとしたら...

あっ!

やっちゃった。
見事に、A画面の修正でBとCは動かなくなりました。

A画面「BとCが勝手に再利用してるのが悪いんだ」

なるほど確かに、BとCもメソッド呼び出し時にもっとちゃんと気をつけるべきでは?

B画面「再利用してよさそうなメソッド名してたし」

A画面さん、再利用される想定ではなく作ってるのであれば、Daoメソッド名に "selectForA画面()" とか、露骨な名前を付ければよかったのでは?

でも、なかなかそんな負けた感たっぷりのメソッド名、かっこ悪くて付けづらいです。そこはなんだかDaoレイヤであることを少し意識して、画面には依存していない "それっぽい名前" を付けるでしょう。なんとなく...

こうやって、再利用が曖昧なメソッドが、Daoメソッドに大量にできあがっていきます。

再利用すべきでないものが、再利用してもよさそうなレイヤに実装されている罠

特に事業会社でのインクリメンタル開発では、これがボディブローのように効きます。人もどんどん入れ替わりますから、経験者でも罠に引っかかりやすいです。

そうなると、もうDaoメソッドに対する信頼がありません。「このDaoメソッド、ほんとに呼んで大丈夫なのか...」本当に再利用できるDaoメソッドまでくすんでしまいます。

ルーズな再利用が、しっかりした再利用を引きずり落とす

ちなみにこれは、単に検索の話に限りません。「ビジネスロジックでもまったく同じ」です。

引数リモコンパターン

「さすがにそんな最小公倍数はやらないよ!」
デグレも考えてなんとか工夫する!」
とします。

そこでできあがるのが、"引数リモコンパターン" です。これは、こちらの記事をお読みください。(また読んだら戻って来てくださいね^^)

// メンテ不能の強者、引数リモコンパターン
http://d.hatena.ne.jp/jflute/20160905/argremocon

public void index() {
    // what do you select?
    ... = logic.selectNandemoMember(null, "S", null
            , false, true, false, CDef.MemberStatus.Formalized
            , true, false, true, true);
}

B画面やC画面が、自分たちに必要な分だけの検索を引数でどうにか...A画面も修正時は引数でどうにか...
そして...

再利用しまくって、メンテするの怖いから、パフォーマンスチューニングもできない

もう一度言いますが、これ最悪です。事業会社では致命的なビジネスインパクトがあります。

入り組んだ分岐で検索の全体像が見えないので、チューニングによる条件表現の変更もしづらい。見事なデグレが怖いから。

ルーズなDaoパターンはまだまだ続きます。

引数めっちゃ画面依存パターン

検索条件をどうやってDaoに渡すのでしょうか?例えば、Formクラスにリクエストパタメーターが詰まっているとします。

Daoレイヤは、通常Webには依存しません。というか、通常画面仕様にも依存しません。

Formの値を、Daoレイヤが受け入れるためのBeanクラスとかに詰め替えてメソッドを呼びます。

A画面のFormを引数にしていたら、B画面が呼べません。A画面のFormを使ったらそれこそA画面に依存してしまいます。なので、再利用するためには必要なことです。

でも確かに面倒ですね。
なので...

めでたく、A画面のFormを引数の形にしたDaoメソッドのできあがり。

// Dao method
public List<Member> selectMemberList(AForm form) { // Form?
    ...
}

なんか、先ほどの最小公倍数や見事なデグレを考えると、その方がへたな再利用されないからいいのかな!?って皮肉な考えも理解できなくもないです。

ただ...「そのDaoクラスは何のためのDaoなのか?」単にパッケージとクラス名が違うだけで、事実上A画面のものじゃん!

でも、そういう体裁になってない。紛らわしいことこの上ない。

もちろん良いことは?

もちろん、Daoパターン自体には良いことがあります。

DBアクセスを隠蔽することで、DBが変わった時でも画面側に影響なく変更ができます。

ほんとに?

ちゃんとしたDaoパターンのデザインができていれば、できるでしょう。

ですが、それ自体がなかなかハードルが高い特にRDBの検索はパフォーマンス論理などが絡むため、クラス参照的には画面から切り離されていたとしても、潜在的に依存しているということがよくあるからです。

少なくとも "ルーズなDaoパターン" では、その潜在性を排除できないでしょう。(というか、排除できてないからルーズなDaoパターン)

また、DBを何かに差し替えるような変更、そんなしょっちゅうはありません。Daoパターンでなくても変更できないわけじゃないので、その辺の費用対効果をどう考えるか?

...

もう一つ、一つのレイヤにDB検索がすべて集っているので、横断的に見て管理しやすい、というポイントがあります。

もし、このメリットを重宝したいなら、A画面のFormクラスを引数にしたDaoメソッドだとしても、まあ100歩譲れば譲れなくはないかなと思います。

でも、そのメリットを本当に享受してますか?Daoを横断して管理するような役割の人が、プロジェクトに本当にいますか?結局ただ置いてるだけだったら、もったいない。

...

さらにもう一つ、縦割りのリソース配分がしやすくなります。

でも、縦割りマネジメントには別の問題も発生するでしょう。代表的なのは、互いが互いのレイヤに対して無関心になること。とある画面、そこで動くプログラムの全体を誰も把握していない。そこから様々なすれ違いが発生します。

少なくともjfluteは、Daoというレイヤを基準に、ディベロッパーの縦割り配置はしないでしょう。

ちゃんとDaoパターンするには?

jfluteの経験上、

「何の思想と工夫もなくただDaoパターンを採用」すると、ほぼ間違いなく今までのアンチパターンが発生していました。

ちゃんとやるなら...

Daoを横断して管理する専門レビューワー

という役割の人が必要だと考えます。おかしなことになってないか常にチェック。ディベロッパーがメソッドデザインに悩んでいたら、相談に乗ってあげる。率先して悩みを見つけてあげる。ときに、その人がリファクタリングもします。

これはDao限らず再利用レイヤでも同じことが言えます。だがしかし、大抵こういう人はいません。

なかなかそういう役割を重宝してマネジメントされることはなかなかないでしょう。

仮にそういう役割が明確にできたとしても、その任務をしっかり遂行できる人がいるでしょうか?技術力も必要だし、整理整頓能力も必要だし、コミュニケーション能力も必要だし、業務知識も必要だし、

業務ドメインをしっかり意識できるか?

仮にできる人がいて、その任務をその人がずっと続けてくれるでしょうか?地味でつまらない仕事です。

やはり DBFlute では Dao はオススメしない

DBFluteには、LazyLoad がありません。Entityのgetメソッドを呼びだしたら、その場でSQLを発行してデータを持ってくる機能です。

高機能なO/Rマッパーには備わってることが多いですが、DBFluteはあえてこの機能をサポートしていません。RDBのデータは明示的に取得を宣言する方が良いと考えているからです。

別のマシンのハードディスクからデータを持って来ているという意識、これを失いたくないからです。コードを見れば何を転送しているのかわかるように。

LazyLoadが備わっていれば、Daoパターンは比較的やりやすいように思います。少なくとも、先ほどのアンチパターンの幾つかは、自然と防ぐことができるからです。その代わり別の問題も発生するかもしれません。そこはトレードオフです。

...

ということから、"Daoパターンでないと解決しない問題" というのが現場で出てこない限りは、DBFluteとしてはなかなかオススメはできません。やるならやるでルーズにならないように。

すでにある程度のDaoがある!?

一方で、DBFlute の Behavior は、すでにDaoレイヤと言えるかもしれません。DB検索の面倒な手続きを隠蔽しています。ただ、DB検索の指示は自分ではしません。

A画面が必要なデータはA画面が決める

そういえば、A画面って言ってますが、A業務とかAロジックと言い代えられます。

Aロジックが必要なデータはAロジックが決める

ConditionBeanがそれに近いかもですね。検索要件だけを ConditionBean で表現して、手続きはBehaviorにお願いする。

「DBの手続きを隠蔽する」という意味合いでは、すでにある程度の再利用は済んでいるとも言えます。

where句の部品再利用

引数リモコンパターンのところでだいぶ語りましたが、DBFluteでは、検索ロジックは部品単位で再利用する、のがオススメであると。

Exクラスに定義する ArrangeQuery です。
 => where句の再利用 | DBFlute

memberBhv.selectList(cb -> {
    cb.setupSelect_...

    cb.query().arrangeServiceMember(); // *here
    cb.query().set...

    cb.query().addOrderBy_...
}

少なくとも事業会社でのインクリメンタル開発では、再利用は慎重に。ルーズにならないことが大切でしょう。

再利用だけがメンテナンスしやすさの指標じゃない。一方で、再利用はぼくらには欠かせないもの。

でも、ぴったり必要な分だけ再利用する、というのはほぼ不可能です。その "ぴったり" を探そうと努力はしますが、どうしても「再利用し過ぎ or 再利用し足りてない」のどちらかに寄ってしまいます。

再利用し過ぎてるくらいであれば、再利用し足りてない方がまだマシ。

再利用し過ぎると、本当に再利用したいものが見えなくなる。信頼できる再利用メソッドを守りたい。

そんなバランス感覚です。

ファイル分割と再利用は別問題

ときどき、

「Daoパターンじゃないと、ActionやServiceが肥大化する!」

と聞くことがありますが、ファイル分割と再利用は別問題です。

ですし...

「肥大化してるからDaoに持っていく」

という理由で持って来たDaoメソッドこそ、ルーズなDaoメソッドと言えるでしょう。どうせ作るなら、そんな理由じゃなく作って欲しい。

画面側、業務側のクラスをどういう粒度、どういうポリシーで切り分けていくか?もともと存在する、また別の課題ですね。

LastaFluteでは、Assistクラスという概念を導入して、再利用するものと再利用しないものを明確に分けつつ、気軽にファイル分割もしていくポリシーを提案しています。
 => Actionの実装デザイン | LastaFlute

何をどこに実装するか?

DB検索は Dao に書くべき。Controller にベタベタ書かず Service に書くべき。なんとかはなんとかに書くべき。

さまざまな「べき」をよく聞きます。

別に間違ってるわけじゃないですが、なんでそこに書くのか?

そこをちゃんと理解せずに実装していたら、その本来のメリットをしっかり享受するメソッドデザインができないでしょう。

そこをちゃんと理解せずに実装していたら、そのとき発生するデメリットを回避するメソッドデザインができないでしょう。

"ここ" に書くとメリットがこうでデメリットがこう。
"そこ" に書くとメリットがこうでデメリットがこう。

しかもそれは、使っているフレームワーク、そのプロジェクトメンバーの状況、そのサービスの特性、今後の展望、それらによって微妙に変わってきます。

安易に型にはめただけの浅い判断で、あっちだこっちだと言っていても、どのレイヤでもルーズになってしまうでしょう。