在某些情况下,将数据直接注入需要它的消费者可能会起作用,但在大多数情况下,消费者通过查询这些数据(使用提供的参数请求数据)来确定他需要什么数据。
对于可以直接注入数据的少数情况(您可以将它们视为无参数查询),直接注入数据会使您的 DI 配置极其复杂。以一个依赖于系统当前时间的消费者为例。您可以将 aDateTime
注入消费者(甚至是 a Lazy<DateTime>
)。另一个消费者可能需要当前用户的出生日期,因此该消费者还依赖于DateTime
. 但是现在你在系统中有歧义,因为DateTime
依赖有两个含义。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>
实现,而无需任何人知道。编写一个将结果缓存在tread-local stogage中的装饰器是不费吹灰之力的:
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 容器都允许您注册这些装饰器并允许您有条件地注册装饰器(基于装饰类型的类型信息)。这允许您将此缓存装饰器仅放置在可以安全缓存的类型上(根据您的条件)。
您可以在本文中找到有关此模型的更多信息:同时……在我的架构的查询端。