[解決済み] よく設計されたクエリーコマンドと仕様
質問
典型的なリポジトリパターンが示す問題(特殊なクエリのためのメソッドの増加など)に対する良い解決策をかなり長い間探しています。 http://ayende.com/blog/3955/repository-is-the-new-singleton ).
私は、Command クエリ、特に Specification パターンを使用したクエリのアイデアがとても好きです。 しかし、仕様に関する私の問題は、それが単純な選択の基準 (基本的に、where 節) にのみ関係し、結合、グループ化、サブセットの選択または投影など、クエリの他の問題を扱わないということです。
(注意: 私は Command パターンのように、quot;command" という用語を使用します(query オブジェクトとしても知られています)。 コマンド/クエリ分離のように、クエリとコマンド (更新、削除、挿入) を区別して話しているのではありません。)
ですから、私はクエリ全体をカプセル化し、かつ、コマンドクラスの爆発のためにスパゲッティリポジトリを交換するだけではない、十分に柔軟な代替品を探しているのです。
私は、たとえば Linqspecs を使用しましたが、選択基準に意味のある名前を割り当てることができることにいくらかの価値を見いだしましたが、それだけでは十分ではありません。 おそらく私は、複数のアプローチを組み合わせた混合ソリューションを求めているのだと思います。
私は、この問題に対処するため、または別の問題に対処するがこれらの要件は満たすために、他の人が開発した可能性のあるソリューションを探しています。 リンクされた記事で、Ayende は nHibernate コンテキストを直接使用することを提案していますが、ビジネス層がクエリ情報を含む必要があるため、大きく複雑になっていると感じます。
私は、待機期間が過ぎたらすぐに、この件に関して報奨金を提供するつもりです。 ですから、あなたのソリューションを賞金に値するものにしてください、良い説明とともに、私は最も良いソリューションを選択し、次点者をアップヴォートします。
注:私はORMベースのものを探しています。 EF または nHibernate である必要はありませんが、これらは最も一般的であり、最も適していると思われます。 もし、他のORMに簡単に適応できるのであれば、それはボーナスになります。 また、Linq互換性があるとよいでしょう。
UPDATE: ここにあまり良い提案がないことに本当に驚いています。 人々は完全に CQRS か、完全にリポジトリ派であるかのどちらかのように見えます。 私のアプリのほとんどは、CQRS を保証するほど複雑ではありません (ほとんどの CQRS 支持者は、CQRS を使用すべきではないと簡単に言いますが)。
UPDATE: ここで少し混乱があるようです。 私は新しいデータ アクセス テクノロジーを探しているのではなく、ビジネスとデータの間の合理的によく設計されたインターフェイスを探しているのです。
理想的には、Queryオブジェクト、Specificationパターン、リポジトリの間のある種の交差を求める。 上で述べたように、Specificationパターンはwhere句の側面だけを扱い、joinやsub-selectなど、クエリの他の側面は扱わない。 リポジトリはクエリ全体を扱いますが、しばらくすると手に負えなくなります。 クエリオブジェクトもクエリ全体を扱いますが、クエリオブジェクトの爆発でリポジトリを単純に置き換えることはしたくありません。
どのように解決するのか?
免責事項です。 まだ素晴らしい回答がないので、私がしばらく前に読んだ素晴らしいブログ記事の一部を、ほぼそのままコピーして掲載することにしました。ブログの記事全文はこちらでご覧いただけます。 はこちら . というわけで、こちらです。
以下の2つのインターフェイスを定義することができます。
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
は
IQuery<TResult>
を使って特定のクエリを定義するメッセージを指定し、それが返すデータを
TResult
ジェネリックタイプを使用します。先に定義したインターフェースで、次のようなクエリメッセージを定義することができます。
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
このクラスは、2つのパラメータを持つクエリ操作を定義し、その結果、配列は
User
オブジェクトを生成します。このメッセージを処理するクラスは,次のように定義できます.
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly NorthwindUnitOfWork db;
public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
}
}
これで、消費者が汎用的な
IQueryHandler
インターフェースに依存させることができます。
public class UserController : Controller
{
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;
public UserController(
IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
{
this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
User[] users = this.findUsersBySearchTextHandler.Handle(query);
return View(users);
}
}
すぐにこのモデルは多くの柔軟性を与えてくれます。
UserController
. を変更することなく、全く別の実装を注入したり、実際の実装をラップしたものを注入したりすることができます。
UserController
(そしてそのインターフェースの他の全ての消費者) に変更を加えることなく、完全に異なる実装、または本当の実装をラップしたものを注入することができます。
は
IQuery<TResult>
を指定したり注入したりする際のコンパイル時のサポートを提供します。
IQueryHandlers
を指定する際のコンパイル時のサポートを提供します。を変更すると
FindUsersBySearchTextQuery
を返すように変更すると
UserInfo[]
を返すのではなく、(実装することで
IQuery<UserInfo[]>
を実装することで、)
UserController
の総称型制約があるため、コンパイルに失敗します。
IQueryHandler<TQuery, TResult>
をマップすることができないからです。
FindUsersBySearchTextQuery
を
User[]
.
を注入することで
IQueryHandler
インターフェイスをコンシューマに注入することは、しかし、まだ対処する必要のある、あまり目立たない問題があります。コンシューマの依存関係の数が大きくなりすぎて、コンストラクタのオーバーインジェクション (コンストラクタがあまりにも多くの引数を取ること) につながる可能性があります。クラスが実行するクエリの数は頻繁に変わる可能性があり、コンストラクタの引数の数を常に変更する必要があります。
を注入しなければならないという問題を修正することができます。
IQueryHandlers
を注入しなければならないという問題を解決することができます。コンシューマとクエリハンドラの間に位置するメディエータを作成します。
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
は
IQueryProcessor
は1つの汎用メソッドを持つ非汎用インターフェイスです。インターフェイスの定義でわかるように
IQueryProcessor
に依存しています。
IQuery<TResult>
インターフェースに依存します。このため、コンパイル時に
IQueryProcessor
. を書き換えてみましょう。
UserController
を書き換えて、新しい
IQueryProcessor
:
public class UserController : Controller
{
private IQueryProcessor queryProcessor;
public UserController(IQueryProcessor queryProcessor)
{
this.queryProcessor = queryProcessor;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
// Note how we omit the generic type argument,
// but still have type safety.
User[] users = this.queryProcessor.Process(query);
return this.View(users);
}
}
は
UserController
に依存するようになりました。
IQueryProcessor
に依存しています。この
UserController
's
SearchUsers
メソッドは
IQueryProcessor.Process
メソッドを呼び出し、初期化されたクエリーオブジェクトを渡します。メソッドは
FindUsersBySearchTextQuery
を実装しているので
IQuery<User[]>
インターフェイスを実装しているので、それを一般的な
Execute<TResult>(IQuery<TResult> query)
メソッドに渡すことができます。C#の型推論のおかげで、コンパイラはジェネリックの型を決定することができ、これによって型を明示的に記述する必要がなくなります。メソッドの戻り値の型は
Process
メソッドの戻り値の型もわかっています。
の実装の責任になりました。
IQueryProcessor
を見つけるのは
IQueryHandler
. これには動的型付けと、オプションで依存性注入フレームワークを使用する必要がありますが、すべて数行のコードで行うことができます。
sealed class QueryProcessor : IQueryProcessor
{
private readonly Container container;
public QueryProcessor(Container container)
{
this.container = container;
}
[DebuggerStepThrough]
public TResult Process<TResult>(IQuery<TResult> query)
{
var handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}
は
QueryProcessor
クラスは、特定の
IQueryHandler<TQuery, TResult>
を構築します。この型は、提供されたコンテナクラスにその型のインスタンスを取得するよう依頼するために使用されます。残念ながら、私たちは
Handle
メソッドをリフレクションを使って呼び出す必要があります (この場合、C# 4.0 dymamic キーワードを使用します)。
TQuery
引数がコンパイル時に利用できないため、この時点でハンドラインスタンスをキャストすることができないからです。しかし
Handle
メソッドの名前が変更されるか、他の引数を取得しない限り、この呼び出しは決して失敗しませんし、もし望むなら、このクラスのためのユニットテストを書くのはとても簡単です。リフレクションを使用すると、若干の低下が生じますが、本当に心配することは何もありません。
あなたの懸念の 1 つに答えます。
ですから、私はクエリ全体をカプセル化する代替手段を探しています。 しかし、スパゲッティを交換するだけでなく、十分な柔軟性があります。 リポジトリとコマンドクラスの交換にならない程度の柔軟性があります。
この設計を使用することの結果として、システム内に多くの小さなクラスが存在することになりますが、多くの小さな/フォーカスされたクラス(明確な名前を持つ)を持つことは良いことです。この方法は、リポジトリにある同じメソッドに対して異なるパラメータを持つ多くのオーバーロードを持つよりも明らかに優れており、それらを1つのクエリクラスにまとめることができます。つまり、リポジトリにあるメソッドよりも、クエリクラスの方がずっと少ないのです。
関連
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み] C#のStringとstringの違いは何ですか?
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] C#で同期メソッドから非同期メソッドを呼び出すには?
-
[解決済み] アグリゲート・ルートって何?
-
[解決済み] LINQ Orderby Descending Query(LINQ降順クエリ
-
[解決済み] シンプルで安全でない双方向のデータ「難読化」?
-
[解決済み】大文字・小文字を区別しない「Contains(string)
-
[解決済み】IEnumerable vs List - What to Use? どのように動作するのでしょうか?
-
[解決済み】PHPで適切なリポジトリパターンを設計するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み] [Solved] アセンブリ System.Web.Extensions dll はどこにありますか?
-
[解決済み] UnityでOnCollisionEnterが呼ばれない
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】2つ(またはそれ以上)のリストを1つに統合する(C# .NETで
-
[解決済み】画像のペイントにTextureBrushを使用する方法
-
[解決済み】WebResource.axdとは何ですか?