データを必要とするコンシューマーに直接データを注入することは、状況によっては機能する場合がありますが、ほとんどの場合、このデータをクエリする(提供された引数を使用してデータを要求する)ことにより、必要なデータを決定するのはコンシューマーです。
データを直接注入できる(パラメーターのないクエリとして表示できる)いくつかのケースでは、データを直接注入すると、DI構成が非常に複雑になります。たとえば、システムの現在の時刻に依存する消費者を考えてみましょう。DateTime
をコンシューマー(または)に注入することができLazy<DateTime>
ます。別の消費者は現在のユーザーの生年月日を必要とする可能性があるため、この消費者もに依存しますDateTime
。しかし、DateTime
依存関係には2つの意味があるため、システムにあいまいさが生じます。DIコンテナーはこれを処理するのが苦手であり、これを解決するには、コンテナーDateTime
を必要とする各コンシューマーにとって何を意味するかをコンテナーに明示的に指示する必要があります。これにより、構成が脆弱になり、封じ込めが困難になります。
この後者の場合(パラメーターなしのクエリ)の解決策は、解決できる明確なインターフェースを定義することです。上記の例では、ITimeProvider
インターフェースとインターフェースを定義できIUserContext
ます。各コンシューマーは、正しいインターフェースに依存することができます。
前者の場合(パラメーター化されたクエリ)、このためのフレームワークは必要ありません。適切な設計が必要です。
あなたはデータベースとWebサービスのクエリについて話していて、返されたデータをキャッシュする方法が必要です。したがって、必要なのはクエリを定義するための抽象化であり、キャッシュやその他の横断的関心事をプラグイン可能でコードに変更を加える必要がない方法で適用できるようにします。
これを行う効果的な方法は、クエリオブジェクト(クエリの入力パラメータ)+リターンタイプを定義するインターフェイスと、そのクエリを処理するロジックを定義するインターフェイスを定義することです。
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
これらの抽象化を使用すると、次のようなクエリオブジェクトを定義できます。
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
このオブジェクトは、検索テキストによってユーザーを検索し、非アクティブなユーザーを含めたり除外したりできるクエリを定義します。クエリ結果はUser
オブジェクトの配列です。
このクエリを実行するロジックは、次のように実装できます。
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly NorthwindUnitOfWork db;
public FindUsersBySearchTextQueryHandler(
NorthwindUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
return (
from user in this.db.Users
where user.Name.Contains(query.SearchText)
where user.IsActive || query.IncludeInactiveUsers
select user)
.ToArray();
}
}
そして、なぜこれがあなたが抱えている問題を正確に解決するのですか?これにより、コンシューマーはインターフェースに依存できるため、問題は解決しますが、誰も知らなくても、実装をデコレーターIQueryHandler<FindUsersBySearchTextQuery, User[]>
でラップできます。結果をトレッドローカルストゲージにキャッシュするデコレータを作成するのは簡単です。IQueryHandler<T>
public class TlsCachingQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
where TResult : class
{
[ThreadStatic]
private static TResult cache;
private readonly IQueryHandler<TQuery, TResult> decorated;
public ValidationQueryHandlerDecorator(
IQueryHandler<TQuery, TResult> decorated)
{
this.decorated = decorated;
}
public TResult Handle(TQuery query)
{
return cache ?? (cache = this.decorated.Handle(query));
}
}
ソリッドDIコンテナを使用すると、これらのデコレータを登録し、(装飾されたタイプのタイプ情報に基づいて)条件付きでデコレータを登録できます。これにより、このキャッシングデコレータを(条件に応じて)安全にキャッシュできるタイプにのみ配置できます。
このモデルの詳細については、この記事を参照してください。一方、私のアーキテクチャのクエリ側で。