コントローラーでSQLを回避するための戦略...またはモデルにいくつのメソッドを含める必要がありますか?


17

したがって、私がかなり頻繁に遭遇する状況は、私のモデルが次のいずれかを開始する状況です。

  • たくさんの方法でモンスターに成長する

または

  • SQLの一部をそれらに渡すことができるため、数百万の異なるメソッドを必要としないほど柔軟です

たとえば、「ウィジェット」モデルがあるとします。いくつかの基本的な方法から始めます。

  • get($ id)
  • insert($ record)
  • update($ id、$ record)
  • 削除($ id)
  • getList()//ウィジェットのリストを取得

それはすべてうまくできていますが、いくつかのレポートが必要です。

  • listCreatedBetween($ start_date、$ end_date)
  • listPurchasedBetween($ start_date、$ end_date)
  • listOfPending()

そして、レポートは複雑になり始めます。

  • listPendingCreatedBetween($ start_date、$ end_date)
  • listForCustomer($ customer_id)
  • listPendingCreatedBetweenForCustomer($ customer_id、$ start_date、$ end_date)

これがどこで成長しているのかを見ることができます...最終的には、非常に多くの特定のクエリ要件があるため、大量のメソッドを実装するか、単一の-> query(query $ query)メソッド...

...または単に弾丸を噛んで、次のようなことを始めてください:

  • list = MyModel-> query( "start_date> X AND end_date <Y AND pending = 1 AND customer_id = Z")

5,000万の他のより具体的なメソッドの代わりに、そのようなメソッドを1つだけ持つことには特定の魅力があります...

このような状況を処理する「正しい」方法はありますか?そのようなクエリをジェネリック-> query()メソッドに詰め込むことは受け入れられるように思えますか?

より良い戦略はありますか?


非MVCプロジェクトで今、この同じ問題を経験しています。データアクセスレイヤーがすべてのストアドプロシージャを抽象化し、ビジネスロジックレイヤーデータベースに依存しないようにするか、ビジネスレイヤーが基礎となるデータベースについて何かを知っているという犠牲を払って、データアクセスレイヤーを汎用にする必要があるのか​​、という疑問が持ち上がりますか?おそらく中間的な解決策は、ExecuteSP(string spName、params object [] parameters)のようなものを用意し、ビジネスレイヤーが読み取るための構成ファイルにすべてのSP名を含めることです。しかし、これに対する答えはあまりよくありません。
グレッグジャクソン

回答:


10

マーティンファウラーのエンタープライズアプリケーションアーキテクチャパターンでは、クエリオブジェクトの使用など、ORMに関連する多くのパターンについて説明しています。

クエリオブジェクトを使用すると、各クエリのロジックを個別に管理および維持される戦略オブジェクトに分離することにより、単一責任の原則に従うことができます。コントローラーで使用を直接管理するか、それをセカンダリコントローラーまたはヘルパーオブジェクトに委任できます。

たくさんありますか?もちろん。一部を一般的なクエリにグループ化できますか?はい、もう一度。

依存性注入を使用して、メタデータからオブジェクトを作成できますか?それがほとんどのORMツールの機能です。


4

これを行う正しい方法はありません。多くの人がORMを使用して、複雑さをすべて取り除きます。より高度なORMの一部は、コード式を複雑なSQLステートメントに変換します。ORMには欠点もありますが、多くのアプリケーションでは、メリットがコストを上回ります。

大規模なデータセットを使用していない場合、最も簡単なことは、テーブル全体をメモリに選択し、コードでフィルター処理することです。

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

内部レポートアプリケーションの場合、このアプローチはおそらく問題ありません。データセットが非常に大きい場合は、テーブルに適切なインデックスだけでなく多くのカスタムメソッドが必要になります。


1
+ 1「これを行う正しい方法はありません」
-ozz

1
残念ながら、データセットの外部でのフィルタリングは、実際に使用する最小のデータセットでもオプションではありません。遅すぎるだけです。:-(ただし、他の人が私の同じ問題にぶつかると聞いてうれしいです。:
キースパーマージュニア

好奇心から@KeithPalmer、あなたのテーブルはどれくらい大きいですか?
ダン

数十万行以上ではありません。データベース外で許容可能なパフォーマンスでフィルタリングするには多すぎます。特に、データベースがアプリケーションと同じマシン上にない分散アーキテクチャの場合はフィルタリングが多すぎます。
キースパーマージュニア

「これを行う正しい方法はありません」の場合は-1。いくつかの正しい方法があります。OPが行っていたように機能を追加するときにメソッドの数を2倍にすることはスケーラブルではなく、ここで提案する代替手段は、クエリ機能の数ではなくデータベースサイズに関して同様にスケーラブルではありません。スケーラブルなアプローチは存在します。他の回答をご覧ください。
セオドアマードック

4

一部のORMでは、基本的な方法から複雑なクエリを作成できます。例えば

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

Django ORMで完全に有効なクエリです。

アイデアはPurchase.objects、内部ステータスがクエリに関する情報を表すクエリビルダー(この場合)があることです。以下のような方法にはgetfilterexcludeorder_by有効で、更新されたステータスで新しいクエリビルダを返します。これらのオブジェクトは反復可能なインターフェイスを実装しているため、オブジェクトを反復処理すると、クエリが実行され、これまでに作成されたクエリの結果を取得できます。この例はDjangoから取得したものですが、他の多くのORMでも同じ構造が見られます。


old_purchases = Purchases.query( "date> date.today()AND type = Purchase.PRESENT AND status!= Purchase.REJECTED");のようなものに対してこれがどのような利点があるかわかりません。SQLのANDとORをメソッドのANDとORにするだけで複雑さを軽減したり、何かを抽象化したりするのではなく、ANDとORの表現を変更するだけですよね?
キースパーマージュニア

4
実際はそうではありません。SQLを抽象化しているため、多くの利点が得られます。まず、注射を避けます。次に、ORMがこれを処理するため、SQLダイアレクトのわずかに異なるバージョンを心配することなく、基になるデータベースを変更できます。多くの場合、気付かずにNoSQLバックエンドを配置することもできます。第三に、これらのクエリビルダーは他のオブジェクトと同様に渡すことができるオブジェクトです。お使いのモデルが半分クエリを作成することができます(たとえば、あなたが最も一般的なケースのためのいくつかのメソッドを持つことができる)と、...処理するために、コントローラに改良することができることをこれは意味
アンドレア・

2
...最も具体的なケース。典型的な例は、Djangoでモデルのデフォルトの順序を定義することです。特に指定しない限り、すべてのクエリ結果はその順序に従います。第4に、パフォーマンス上の理由でデータを非正規化する必要がある場合は、すべてのクエリを書き換えるのではなく、ORMを微調整するだけで済みます。
アンドレア

+1上記のような動的クエリ言語、およびLINQの場合。
エヴァンプライス

2

3番目のアプローチがあります。

あなたの特定の例は、必要な機能の数が増えるにつれて必要なメソッドの数が指数関数的に増加することを示しています:すべてのクエリ機能を組み合わせて高度なクエリを提供する機能が必要です...基本クエリ、1つのオプション機能を追加する場合は2、2つ追加する場合は4、3つ追加する場合は8、n個の機能を追加する場合は2 ^ n。

それは明らかに3つまたは4つの機能を超えて維持できず、メソッド間でほとんどコピーアンドペーストされている多くの密接に関連するコードの悪臭があります。

データオブジェクトを追加してパラメーターを保持し、提供された(または提供されていない)パラメーターのセットに基づいてクエリを作成する単一のメソッドを使用することで、これを回避できます。その場合、日付範囲などの新機能の追加は、日付範囲のセッターとゲッターをデータオブジェクトに追加してから、パラメーター化されたクエリが構築されるコードを少し追加するだけです。

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

...およびパラメータがクエリに追加される場所:

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

このアプローチは、機能が追加されるにつれて線形のコードの成長を可能にし、パラメータ化されていない任意のクエリを許可する必要はありません。


0

一般的なコンセンサスは、MVCのモデルでできるだけ多くのデータアクセスを維持することだと思います。他の設計原則の1つは、より一般的なクエリ(モデルに直接関連していないクエリ)を、より抽象的でより高いレベルに移動して、他のモデルでも使用できるようにすることです。(RoRには、フレームワークと呼ばれるものがあります)また、考慮しなければならない別のことがあります。それがコードの保守性です。プロジェクトが成長するにつれて、コントローラーにデータアクセスがある場合、それを追跡することがますます困難になります(現在、巨大なプロジェクトでこの問題に直面しています)。最終的にはテーブルからのクエリになる可能性があります。(これはまた、コードの再利用につながる可能性があり、これは有益です)


1
あなたが話していることの例...?
キースパーマージュニア

0

サービスレイヤーインターフェイスには多くのメソッドがありますが、データベースへの呼び出しには1つしかありません。

データベースには4つの主要な操作があります

  • インサート
  • 更新
  • 削除する
  • 問い合わせ

別のオプションの方法は、基本的なDB操作に該当しないデータベース操作を実行することです。そのExecuteを呼び出しましょう。

挿入と更新は、保存と呼ばれる1つの操作に結合できます。

メソッドの多くはクエリのものです。したがって、当面のニーズのほとんどを満たす汎用インターフェースを作成できます。汎用インターフェイスのサンプルを次に示します。

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

データ転送オブジェクトは汎用であり、すべてのフィルター、パラメーター、並べ替えなどが含まれます。データレイヤーは、これを解析および抽出し、ストアドプロシージャ、パラメーター化されたsql、linqなどを介してデータベースへの操作を設定します。したがって、SQLはレイヤー間で渡されません。これは通常ORMが行うことですが、独自のロールを作成して独自のマッピングを作成することもできます。

だから、あなたの場合にはウィジェットがあります。ウィジェットはIPOCOインターフェースを実装します。

そのため、サービスレイヤーモデルには getList().

への変換を処理するマッピングレイヤーが必要getListになります

Search<Widget>(DataTransferObject<Widget> Dto)

およびその逆。他の人が言ったように、これはいつかORMを介して行われますが、最終的には、特に100個のテーブルがある場合、多くのボイラープレートタイプのコードになります。ORMは、パラメータ化されたSQLを魔法のように作成し、データベースに対して実行します。独自にロールする場合、さらにデータレイヤー自体で、SP、linqなどをセットアップするためにマッパーが必要になります(基本的には、データベースに送信されるsql)。

前述のように、DTOは構成によって構成されるオブジェクトです。おそらく、その中に含まれるオブジェクトの1つはQueryParametersと呼ばれるオブジェクトです。これらは、クエリによって設定および使用されるクエリのすべてのパラメータになります。別のオブジェクトは、クエリ、更新、extから返されたオブジェクトのリストです。これがペイロードです。この場合、ペイロードはウィジェットリストのリストになります。

したがって、基本的な戦略は次のとおりです。

  • サービス層呼び出し
  • ある種のリポジトリ/マッピングを使用してデータベースへのサービス層呼び出しを変換する
  • データベース呼び出し

あなたの場合、モデルには多くのメソッドを含めることができると思いますが、最適なのはデータベース呼び出しをジェネリックにすることです。まだ多くの定型的なマッピングコード(特にSP)や、パラメーター化されたSQLを動的に作成している魔法のORMコードがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.