EDITH说(TL; DR)
我建议的解决方案的一个变体去;保持所有ICommandHandler
s和IQueryHandler
可能异步并在同步情况下返回已解决的任务。不过,我不希望使用Task.FromResult(...)
所有的地方,所以我为了方便定义的扩展方法:ICommandHandler/IQueryHandler与异步/等待
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)中设计应用程序体系结构。有了这个问题,我希望得到一些关于我偶然想要纳入async/await
的一些问题的意见。注:这是一个相当漫长的一个:-)
我的解决方案的结构是这样的:
MyCompany.Contract
(命令/查询和通用接口)MyCompany.MyProject
(包含业务逻辑和命令/查询处理程序)MyCompany.MyProject.Web
(MVC的Web前端)
我在维护结构和命令查询的Separa读了重刑,发现这些职位非常有帮助:
- Meanwhile on the query side of my architecture
- Meanwhile on the command side of my architecture
- Writing highly maintainable WCF services
到目前为止,我已经得到了我周围的ICommandHandler
/IQueryHandler
概念和依赖注入头(我使用SimpleInjector - 这真的很简单)。
的考虑方法
上述文章的方法提出使用波苏斯作为命令/查询和描述的这些调度员为以下处理程序接口的实现:
interface IQueryHandler<TQuery, TResult>
{
TResult Handle(TQuery query);
}
interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
在MVC控制器你” d。使用本如下:
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容器的帮助下注入
async/await
动力处理程序,但是编译器在编译时不知道这一点,所以MVC处理程序将会惨败,但有一个例外,告诉您该方法在所有异步任务完成执行之前返回。
当然,你可以标记MVC处理程序为async
这就是这个问题。
命令返回值
我想到了给定的方法,并更改了接口,以解决问题1和2中,我补充说,有一个明确的结果类型的ICommandHandler
- 就像IQueryHandler
。这仍然违反CQS但至少它是平原明显,这些命令将返回某种价值与不具有与结果属性杂乱的命令对象的额外好处:
interface ICommandHandler<TCommand, TResult>
{
TResult Handle(TCommand command);
}
当然有人会说,当你命令和查询具有相同的接口为什么要麻烦?但我认为值得以不同的名称命名 - 只是看起来更清洁。
我的初步解决方案
后来我苦苦思索了第三期的手头......我的一些命令/查询处理的需要是异步的(如发出WebRequest
用于验证另一个Web服务)别人不要”吨。所以我想这将是最好的从地上爬起来为async/await
设计我的处理程序 - 这当然泡到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
}
}
尽管这解决了我的问题 - 显而易见的返回值,所有处理程序都可能是异步的 - 它会让我的大脑很难将async
放在一个非异步的东西上。有几个缺点我看到这一点:
- 处理程序接口不是因为我想他们是整齐 - 在
Task<...>
thingys到我的眼睛是很冗长,乍一看混淆,我只希望这样的事实:从一个查询/命令返回一些东西 - 编译器警告你在同步处理程序实现中没有合适的
await
(我想能够编译我的Release
和Warnings as Errors
) - 你可以用一个编译指令来覆盖它...是的。 .. 好 ... - 我可以省略在这些情况下,
async
关键字使编译器高兴,但为了实现处理器接口,你就必须返回某种Task
明确 - 那是相当难看 - 我可以提供的同步和异步版本处理程序接口(或将它们全部放在一个接口中,以扩展实现),但我的理解是,理想情况下,处理程序的使用者不应该意识到命令/查询处理程序是同步的或异步的,因为这是事实一个横切关注点。如果我需要做一个以前的同步命令异步呢?我必须改变处理程序的每个消费者,这可能会破坏我的代码中的语义。
- 在另一方面潜在 -async,处理的方法甚至会给我用我的DI容器的帮助下装饰他们
现在我不改变的同步处理是异步的能力看到一个最好的解决方案...我很茫然。
任何人有类似的问题,我没有想到一个优雅的解决方案?
谢谢,概念其实非常相似......我说我个人的解决问题的办法为方便 – mfeineis