まず具体的にStakOverflowでこの質問をしてみましたが、ここで指摘された後、もっと一般的な言葉で言い換える必要があることに気付きました。ただし、私の特定の使用例についてより具体的な詳細が必要な場合は、引き続き 元の質問 を確認できます。
比較的複雑なレポートインターフェイスを想定します。簡単にするために、結果のデータを表形式でのみ表示しているが、既存のドメインオブジェクトにマッピングされていない集計を表示していると仮定します。ユーザーはカレンダーから開始日/終了日を選択し、特定の基準でレポートを制限し、特定の列で結果を並べ替えることができます。これらはすべてオプションです。リクエストはいくつかのコードレイヤーで処理されますが、処理後、複数のテーブルのデータを集約するために、フィルター条件、並べ替えディレクティブ、およびJOINステートメントを含む単一のSQLクエリになります。
データベース側では、永続性を処理し、厳密なエンティティ/ドメインオブジェクトを実装するように強制するORM(NHibernate)を使用しています。それは明確であり、すでに正常に動作しています。
反対側(ユーザーの方)には、ユーザーにデータを表示するビューと、データの入出力を解釈するコントローラーがあります。それも明らかで、適切に処理されます。
私の質問はこれです:それらのレイヤーの間で何が起こるべきですか?適切なカプセル化に向けて推奨されるアプローチは何ですか?私の直感的なアプローチは、ユーザーの入力に応じて、ビジネスロジック(BL)がクエリオブジェクトのさまざまな部分を徐々に構築するさまざまなクエリヘルパーを呼び出せるようにすることでした。 BLで条件付きでJOINを処理しなければならなくなったとき、それは赤旗を上げ始めました。
私のフレデリックの私の答え StackOverflowに関する前の質問 に同意します。クエリを構築するすべてのコードをより深く、永続化レイヤーに近づけ、BLにモデルに関する知識がないようにします。それは確かによりクリーンなアプローチであり、永続性レベルからコントローラーに向かって、そして最終的にはビューレイヤーまで、データベースの結果を「上向き」に運ぶデータ転送オブジェクト(DTO)を簡単に定義できます。ただし、ユーザーは多くのオプション入力を入力できます。つまり、コントローラーレイヤーから、BLが存在するサービスレベルを経由して、永続化レイヤーのクエリヘルパーまで、データを運ぶ補助クラスも必要になります。これらが何と呼ばれるのかわからないので、「クエリ転送オブジェクト」のことを「QTO」と呼びましょう(SQLではなく「ユーザークエリ」という意味の「クエリ」)。したがって、永続性レベルはこれらのQTOをSQLに解釈し、SQLを実行し、結果セットをDTOに変換して、バブルアップします。
このアプローチの私の問題は、データ転送のためにいくつかの中間クラスを追加するだけですが、処理自体はまったく同じになることです(別のレイヤーでのみ)。それでも、これらすべてのJOINを条件付きで追加し、条件を条件付きで追加し、並べ替えディレクティブを条件付きで追加する必要があります。このときだけ、コントローラーでQTOを条件付きで入力する追加の労力も費やす必要があります。そして、将来のリファクタリングはこれ以上簡単ではありません。QTO構造の変更は、コントローラーとモデルレイヤーの両方に影響します。また、データベース構造への関連する変更により、QTOとDTOの両方が変更されることがほぼ保証されます。上手。
あなたの経験では、これを設計する良い方法は何ですか?私はそれを過剰設計していますか?使用している/提案している設計で、私の複雑さの懸念にどのように対処しますか?または、追加された複雑さを相殺することで見られない利点はありますか?
Shortバージョン:少なくともNHibernateでは、包括的で標準化された設計パターンとこれに対するサポートは存在しないようです。
ロングバージョン
私はこれをオンとオフに調査するために過去数週間を費やしました。多くの検索を行った後、 Andrew Whitaker のブログで、私が使用しているテクノロジ(および私の個人的な好み、つまりQueryOverを使用したNHibernate)に適用できる手法をようやく見つけました:QueryOverシリーズ-パート6:クエリ構築テクニック。
Andrewが説明しているのは、デザインパターンというより、テクニックのほうが多いことを理解しています。ただし、誰かがそのような短いチュートリアルでその特定のトピックに深く取り組む必要性を感じたという事実(9つの部分しかなく、そのうちの1つがこれに対処する)は、これが重大な問題であることを証明し、比較的頻繁に遭遇し、実際パターンとテクニックの両方として(おそらく、基礎となるライブラリで)適切に対処する必要があります。
Andrewのソリューションは、「エイリアスを渡す必要があるかもしれない」セクションまで、非常にエレガントです。それは特に私が遭遇した問題でもあり、私はこれらの質問をここに投稿するように促しました(StackOverflowとこれに関する質問)。
セクション「エイリアスを渡す必要があるかもしれません」のアンドリューの解決策は確かに機能しますが、それは非常に洗練されていないと思います、そしてそれは分離の大きな時間を壊します)。
彼の中間ソリューションを比較する(その最後のセクションの前)
IList<ProductDTO> products = session.QueryOver<Product>()
.ApplyColorFilter(colors)
.ApplyRatingFilter(minimumRating)
.SelectList(list => list
.Select(pr => pr.Name).WithAlias(() => result.Name)
.Select(pr => pr.Color).WithAlias(() => result.Color)
.Select(pr => pr.ListPrice).WithAlias(() => result.ListPrice))
.TransformUsing(Transformers.AliasToBean<ProductDTO>())
.List<ProductDTO>();
return products;
彼の最終的な解決策(そのセクションで説明)
ProductReview reviewAlias = null;
var query = session.QueryOver<Product>()
.JoinAlias(pr => pr.Reviews, () => reviewAlias);
FilterQueryByRating(query, () => reviewAlias);
query
.SelectList(list => list
.SelectGroup(pr => pr.Id)
.SelectMax(() => reviewAlias.Rating))
.List<object[]>();
最初のアプローチは確かに制限されており、対処する必要がありますが(彼の投稿の説明)、それは明らかに、彼の最終的なソリューションよりもはるかにエレガントで、読みやすく、保守可能で、隔離されています。
比較的広く使用されており、活発に開発されているNHibernateには、この問題に対するより良い解決策がないように思われるので、現時点で本当にクリーンな解決策はないと結論付けることができます。動的に生成されたクエリについて話していることを考えると、これは私には驚くべきことです。少なくとも10年間、人々は苦労してきました。
より良い回答がポップアップした場合は、その時点で受け入れられた回答を喜んで更新します(結局、私にも役立ちます。そして、この回答よりも確かに優れています)。
あなたは物事をやりすぎないように注意するのが賢明です。それでも、「複雑なクエリ」問題のコンテキストは、永続化レイヤーの使用を(BLで)抽象化するのに役立つ インタープリターパターン への投資に適しているように思えますが、クエリ目的で?
次に、質問は「どのクエリ言語のインタープリターですか?」になります。それで、私はあなたがあなたが述べた最初のアプローチであなたのBLに書くことになる様々なJOINの形を「じっと見つめる」に戻り、そしてソースクエリ言語になる可能性のある簡単なDSLを考案しようとします最も頻繁で一般的なクエリパターンのドメイン。
別の方法として(「N」を使用してNHibernateについて言及したため)、独自の QueryProvider を実装するオプションもあり、Linqの理解構文がこれらのクエリを構築するのに十分強力であることがわかります。
これが可能な設計の1つです。
私はそれを2つの領域に構成します:
マッピングは、BLオブジェクトとDBオブジェクトのマッピング、関係の構築などを、インとアウトの両方で処理します。 INの場合はマッピングと構築が必要であり、OUT(戻り)の場合はマッピングが必要なだけなので、クエリは往復で構築されないことに注意してください。
クエリの作成では、以前にマップされたオブジェクトを処理してSQLクエリを作成します。 SQLクエリはパーツで構成されているため、これも分解できます。
これを1つのレイヤーに統合することもできますが、ビジネスオブジェクト(DTO)とデータベース(マッピングレイヤー)の間の抽象化の領域が必要だと思います。
次に、マッピング層とは別にクエリの構築をテストできます。また、マッピングの変更がある場合、以前にマッピングされたオブジェクトからクエリを構築するだけなので、マッピングレイヤーでこれを処理し、その後、構築レイヤーで変更をシームレスに処理する必要があります。
たとえば、データベースに別の結合テーブルを追加したとします。マッピング層は、これを処理する必要があります。これにより、別の結合条件が自動的に処理されます。結合の実際の構築に違いはないはずですが、結合条件が1つではなく、2つになりました。
For each (Condition in JoinConditions)....
これにより、各レイヤーがテスト可能になるだけでなく、各レイヤーが単一の責任を持つようになります。
私はこれらのいくつかを見たことがあり、本質的に複雑になることを警告します。いくつかの一般的な操作から始め、いくつかの単純なロジックから始めてから、より複雑で抽象的なロジックを構築します。
基本的に、UIレイヤーからDBへのクエリビルダーメカニズムまたはマッパーが必要です。
すでにすばらしいORMを使用しているので、マッピング部分を作成するだけで済み、NHibernateがクエリの作成を処理します。
マッピング部分では、UI固有のすべてのパラメーターを取得し、クエリビルダーロジックを配置して、最終的に結果を返す必要があります。 UIを変更した場合は、これを最初から実行する必要があります。
クエリ構築の部分については、NHibernate QueryOver Query Building Techniques に関するこの記事を読むことをお勧めします。
コメントのために編集:
UI固有のコードはUIレイヤーにあり、データ固有のコードはリポジトリまたはDALレイヤーにある必要があります。しかし、これらの2つの層はどのように通信できますか?それはトリッキーな部分です。
私は多くの場合、自分の(DB)エンティティとインターフェイスだけでなく、通信目的のみのモデルにも追加のレイヤーを使用します。これはすべてのプロジェクト(UI、ビジネス、サービスなど)に共通です。ただし、その特定のプロジェクトでプロジェクトのみ(UI、ビジネス、サービスなど)モデルを維持することを検討してください。
したがって、エンティティレイヤーで共通のモデルを作成して、UIからDAL /リポジトリへのマッピングを行い、それをUIレイヤー(以前にマッピングと呼びました)に入力してから、DAL /リポジトリレイヤーに送信します。この情報を使用して、クエリを作成し、実行して、結果をUIレイヤーに送り返すことができます。
このアプローチがお役に立てば幸いです。