我完全理解你正在試圖在這裏做。您正在應用SOLID原則,因此查詢實現從消費者中抽象出來,消費者僅發送消息(DTO)並獲得結果。通過爲查詢實現一個通用接口,我們可以用一個裝飾器來包裝實現,它允許各種有趣的行爲,比如事務行爲,審計,性能監視,緩存等等。
這樣做的方法是定義爲一個消息話題下面接口(查詢定義):
public interface IQuery<TResult> { }
並定義以下接口,用於實行:
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
的IQuery<TResult>
是某種毫安的rker接口,但是這使我們能夠靜態地定義什麼是查詢返回,例如:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
實現可以被定義如下:
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly IUnitOfWork db;
public FindUsersBySearchTextQueryHandler(IUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
// example
return (
from user in this.db.Users
where user.Name.Contains(query.SearchText)
where user.IsActive || query.IncludeInactiveUsers
select user)
.ToArray();
}
}
消費者不能拿上IQueryHandler<TQuery, TResult>
到依賴執行查詢:
public class UserController : Controller
{
IQueryHandler<FindUsersBySearchTextQuery, User[]> handler;
public UserController(
IQueryHandler<FindUsersBySearchTextQuery, User[]> handler)
{
this. handler = handler;
}
public View SearchUsers(string searchString)
{
var query = new FindUsersBySearchTextQuery
{
SearchText = searchString,
IncludeInactiveUsers = false
};
User[] users = this.handler.Handle(query);
return this.View(users);
}
}
這允許您添加橫切關注點查詢處理程序,沒有消費者知道這一點,這給你完整的組合物1樂時間支持。但是,這種方法(IMO)最大的缺點是,你會很容易得到大的構造函數,因爲你經常需要執行多個查詢(沒有真正違反SRP)。
爲了解決這種情況,可以引入消費者和IQueryHandler<TQuery, TResult>
接口之間的抽象:
注入多個
IQueryHandler<TQuery, TResult>
實現
public interface IQueryProcessor
{
TResult Execute<TResult>(IQuery<TResult> query);
}
Instread,可以注入一個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
};
// Note how we omit the generic type argument,
// but still have type safety.
User[] users = this.queryProcessor.Execute(query);
return this.View(users);
}
}
的IQueryProcessor
實現可以是這樣的:
sealed class QueryProcessor : IQueryProcessor
{
private readonly Container container;
public QueryProcessor(Container container)
{
this.container = container;
}
[DebuggerStepThrough]
public TResult Execute<TResult>(IQuery<TResult> query)
{
var handlerType = typeof(IQueryHandler<,>)
.MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}
它依賴於容器(它的成分根的一部分),並使用dynamic
分型(C# 4.0)執行查詢。
此IQueryProcessor
實際上是您的QueryFactory
。
雖然使用這個IQueryProcessor
抽象有缺點。例如,您錯過了讓您的DI容器驗證是否存在請求的IQueryHandler<TQuery, TResult>
實現的可能性。在請求根對象時,您會發現此時您調用processor.Execute
。您可以通過編寫一個額外的集成測試來解決這個問題,該測試檢查是否爲實現IQuery<TResult>
的每個類註冊了IQueryHandler<TQuery, TResult>
。另一個缺點是依賴關係不太清晰(IQueryProcessor
是某種環境上下文),這使得單元測試有點困難。例如,當消費者運行新類型的查詢時,您的單元測試仍然會編譯。
您可以在此博客文章中找到有關此設計的更多信息:Meanwhile… on the query side of my architecture。
你真的需要泛型IQuery嗎?用作「方法說明符」而不是「常用方法」的IMO接口是不好的做法,可能會導致這種問題。虛擬接口要好得多。 – Euphoric
@Euphoric:如果可能,我確實需要通用查詢。能夠裝飾查詢將產生相當強大的功能。 – jgauffin
你是什麼意思「裝飾查詢」?另請注意,因此,您需要爲輸入參數至少創建一個類。這不會是問題,如果你讓具體查詢做它自己的執行的執行。 – Euphoric