我去了一个建议的解决方案的变体;保持所有ICommandHandler和IQueryHandlers可能是异步的,并在同步情况下返回已解决的任务.不过,我不想在任何地方使用Task.FromResult(…),所以为了方便起见我定义了一个扩展方法:
public static class TaskExtensions { public static Task<TResult> AsTaskResult<TResult>(this TResult result) { // Or TaskEx.FromResult if you're targeting .NET4.0 // with the Microsoft.BCL.Async package return Task.FromResult(result); } } // Usage in code ... using TaskExtensions; class MySynchronousQueryHandler : IQueryHandler<MyQuery,bool> { public Task<bool> Handle(MyQuery query) { return true.AsTaskResult(); } } class MyAsynchronousQueryHandler : IQueryHandler<MyQuery,bool> { public async Task<bool> Handle(MyQuery query) { return await this.callAWebserviceToReturnTheResult(); } }
可惜C#不是Haskell …而是8-).真的闻起来像Arrows的应用程序.无论如何,希望这有助于任何人.现在我原来的问题:-)
介绍
你好!
对于一个项目,我正在C#(.NET4.5,C#5.0,ASP.NET MVC4)中设计应用程序架构.有了这个问题,我希望得到一些意见,我偶然试图融入异步/等待.注意:这是相当长的一个:-)
我的解决方案结构如下所示:
> MyCompany.Contract(命令/查询和通用接口)
> MyCompany.MyProject(包含业务逻辑和命令/查询处理程序)
> MyCompany.MyProject.Web(MVC Web前端)
我阅读了可维护的架构和命令查询分离,发现这些帖子非常有用:
> Meanwhile on the query side of my architecture
> Meanwhile on the command side of my architecture
> Writing highly maintainable WCF services
到目前为止,我已经围绕着ICommandHandler / IQueryHandler概念和依赖注入(我正在使用SimpleInjector – 它真的很简单).
给定方法
上面的文章的方法建议使用POCO作为命令/查询,并将这些调用器作为以下处理程序接口的实现进行描述:
interface IQueryHandler<TQuery,TResult> { TResult Handle(TQuery query); } interface ICommandHandler<TCommand> { void Handle(TCommand command); }
在MVC控制器中,您可以使用以下内容:
class AuthenticateCommand { // The token to use for authentication public string Token { get; set; } public string SomeResultingSessionId { get; set; } } class AuthenticateController : Controller { private readonly ICommandHandler<AuthenticateCommand> authenticateUser; public AuthenticateController(ICommandHandler<AuthenticateCommand> authenticateUser) { // Injected via DI container this.authenticateUser = authenticateUser; } public ActionResult Index(string externalToken) { var command = new AuthenticateCommand { Token = externalToken }; this.authenticateUser.Handle(command); var sessionId = command.SomeResultingSessionId; // Do some fancy thing with our new found knowledge } }
我对这种方法的一些看法:
>在纯CQS中,只有查询应该返回值,而命令应该是唯一的命令.实际上,命令返回值更方便,而不是发出命令,并且稍后对命令应该首先返回的内容(例如数据库ID等)进行查询.这就是为什么the author suggested将返回值放入命令POCO中.
>从命令返回的内容不是很明显,实际上它看起来像命令是一个火,忘记类型的东西,直到你最终遇到在处理程序运行后被访问的奇怪的结果属性加上命令现在知道它的结果
>处理程序必须是同步的才能工作 – 查询以及命令.使用C#5.0可以使用您喜欢的DI容器的帮助来注入异步/等待功能的处理程序,但是编译器在编译时不知道这一点,所以MVC处理程序会因为异常而失败而失败,告诉你该方法在所有异步任务完成执行之前返回.
当然,你可以将MVC处理程序标记为异步,这就是这个问题.
命令返回值
我考虑了给定的方法,并对接口进行了更改以解决问题1.和2.因为我添加了一个具有显式结果类型的ICommandHandler,就像IQueryHandler一样.这仍然违反了CQS,但是至少很明显的是,这些命令返回某种价值,其附加的好处是不必用result属性来混淆命令对象:
interface ICommandHandler<TCommand,TResult> { TResult Handle(TCommand command); }
自然可以说,当你有相同的命令和查询接口为什么麻烦?但是我认为这是不同的命名,只是看起来更清洁我的眼睛.
我的初步解决方案
然后我想到了第三个问题,我的一些命令/查询处理程序需要是异步的(例如,发布WebRequest到另一个Web服务进行身份验证),而其他人则没有.所以我认为最好是从头开始设计我的处理程序以进行异步/等待 – 这甚至对于MVC处理程序来说,即使是事实上同步的处理程序:
interface IQueryHandler<TQuery,TResult> { Task<TResult> Handle(TQuery query); } interface ICommandHandler<TCommand> { Task Handle(TCommand command); } interface ICommandHandler<TCommand,TResult> { Task<TResult> Handle(TCommand command); } class AuthenticateCommand { // The token to use for authentication public string Token { get; set; } // No more return properties ... }
AuthenticateController:
class AuthenticateController : Controller { private readonly ICommandHandler<AuthenticateCommand,string> authenticateUser; public AuthenticateController(ICommandHandler<AuthenticateCommand,string> authenticateUser) { // Injected via DI container this.authenticateUser = authenticateUser; } public async Task<ActionResult> Index(string externalToken) { var command = new AuthenticateCommand { Token = externalToken }; // It's pretty obvIoUs that the command handler returns something var sessionId = await this.authenticateUser.Handle(command); // Do some fancy thing with our new found knowledge } }
虽然这解决了我的问题 – 明显的返回值,但是所有的处理程序都可以是异步的 – 它伤害了我的大脑将异步放在一个不是异步的东西上,因为它是异常的.我看到有几个缺点:
>处理程序接口不像我想要的那样整齐 – Task< ...>事情对我的眼睛是非常冗长的,一见钟情的事实,我只想从一个查询/命令返回的东西
>编译器警告您,在同步处理程序实现中没有适当的等待(我想要能够编译我的发布与警告作为错误) – 您可以用编译指示覆盖…是啊…好吗?
>我可以在这些情况下省略async关键字,使编译器变得快乐,但是为了实现处理程序接口,你必须明确地返回某种任务 – 这很丑陋
>我可以提供处理器接口的同步和异步版本(或将它们放在一个接口中膨胀实现),但是我的理解是,理想情况下,处理程序的消费者不应该意识到命令/查询处理程序是同步或异步的,因为这是一个交叉关切的问题.如果我需要一个以前的同步命令异步怎么办?我必须改变处理程序的每个消费者,通过代码可能会破坏语义.
>另一方面,潜在的异步处理程序方法甚至可以通过我的DI容器的帮助来将同步处理程序改变为异步
现在我没有看到最好的解决方案…我很失落.
任何人有类似的问题和优雅的解决方案我没有想到?
解决方法
异步周围的设计问题与IDisposable周围的设计问题非常相似;将IDisposable添加到界面是一个突破性的变化,因此您需要知道任何可能的实现可能是一次性的(实现细节).异步存在并行问题;您需要知道任何可能的实现可能是异步的(实现细节).
由于这些原因,我将接口上的任务返回方法视为“可能异步”方法,就像从IDisposable继承的接口意味着“可能拥有资源”.
我知道的最好的方法是:
>定义任何可能与异步签名异步的方法(返回任务/任务< T>).
>返回Task.FromResult(…)用于同步实现.这比没有等待的异步更合适.