mfe*_*eis 15 asp.net-mvc cqrs async-await c#-5.0 simple-injector
我选择了建议解决方案的变体; 保持所有ICommandHandlers和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();
}
}
Run Code Online (Sandbox Code Playgroud)
遗憾的是C#不是Haskell ......还是8-).真的闻起来像箭头的应用.无论如何,希望这有助于任何人.现在我原来的问题:-)
你好!
对于一个项目,我目前正在用C#(.NET4.5,C#5.0,ASP.NET MVC4)设计一个应用程序架构.有了这个问题,我希望能够对我试图融入的一些问题得到一些看法async/await.注意:这是一个冗长的:-)
我的解决方案结构如下所示:
MyCompany.Contract (命令/查询和通用接口)MyCompany.MyProject (包含业务逻辑和命令/查询处理程序)MyCompany.MyProject.Web (MVC网络前端)我阅读了可维护的体系结构和Command-Query-Separation,发现这些帖子非常有用:
到目前为止,我已经了解了ICommandHandler/ IQueryHandlerconcepts和依赖注入(我正在使用SimpleInjector - 它真的很简单).
上述文章的方法建议使用POCO作为命令/查询,并将这些调度程序的调度程序描述为以下处理程序接口的实现:
interface IQueryHandler<TQuery, TResult>
{
TResult Handle(TQuery query);
}
interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)
在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
}
}
Run Code Online (Sandbox Code Playgroud)
我对这种方法的一些看法:
async/await在你最喜欢的DI容器的帮助下注入动力处理程序,但编译器在编译时并不知道这一点,因此MVC处理程序将失败,并且异常会告诉你返回的方法在所有异步任务完成执行之前.当然你可以将MVC处理程序标记为async,这就是这个问题的内容.
我考虑了给定的方法并对接口进行了更改以解决问题1.和2.我添加了一个ICommandHandler具有显式结果类型的方法 - 就像IQueryHandler.这仍然违反了CQS,但至少显而易见的是,这些命令返回某种值,并且具有不必使用result属性使命令对象混乱的额外好处:
interface ICommandHandler<TCommand, TResult>
{
TResult Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)
当然有人可能会争辩说,当你拥有相同的命令和查询界面时,为什么要这么麻烦?但我认为值得以不同的方式命名 - 只是看起来更清洁.
然后我想到了第三个问题......我的一些命令/查询处理程序需要是异步的(例如,发送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 ...
}
Run Code Online (Sandbox Code Playgroud)
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
}
}
Run Code Online (Sandbox Code Playgroud)
虽然这解决了我的问题 - 显而易见的返回值,所有处理程序都可能是异步的 - 这会伤害我的大脑,async因为它只是因为而不是异步的东西.我看到有几个缺点:
Task<...>我眼中的东西非常冗长,乍一看似乎混淆了我只想从查询/命令返回一些东西的事实await的同步处理程序实现(我想能够编译我Release的Warnings as Errors) - 你可以用一个pragma覆盖它...是啊...好吧...async在这些情况下我可以省略关键字以使编译器满意,但是为了实现处理程序接口,你必须Task明确地返回某种类型- 这非常难看现在我没有看到最好的解决方案...我不知所措.
任何人都有类似的问题和优雅的解决方案,我没有想到?
Ste*_*ary 16
Async和await不能与传统的OOP完美搭配.我有一个关于这个主题的博客系列; 您可能会发现异步接口上的帖子特别有用(尽管我没有涵盖您尚未发现的任何内容).
周围的设计问题与周围的设计问题async非常相似IDisposable; 这是一个重大更改添加IDisposable到界面,所以你需要知道的任何可能的实现是否会永远是一次性的(实现细节).存在并行问题async; 你需要知道的任何可能的实现是否会永远是异步的(实现细节).
出于这些原因,我Task将接口上的方法视为"可能异步"的方法,就像继承自IDisposable"可能拥有资源" 的接口一样.
我所知道的最好的方法是:
Task/ Task<T>).Task.FromResult(...)同步实现.这比async没有的更合适await.这种方法几乎就是你已经在做的.对于纯函数式语言,可能存在更理想的解决方案,但我没有看到C#的解决方案.
Ste*_*ven 10
你说:
处理程序的使用者不应该意识到命令/查询处理程序是同步或异步的事实,因为这是一个跨领域的问题
Stephen Clearly已经触及了这一点,但异步不是一个贯穿各领域的问题(或者至少不是它在.NET中实现的方式).Async是一个架构问题,因为你必须事先决定是否使用它,它完全有可能你的所有应用程序代码.它会改变你的界面,因此如果没有应用程序知道它,就不可能"偷偷"进去.
虽然.NET让async更容易,正如你所说,它仍然会伤害你的眼睛和头脑.也许它只需要心理训练,但我真的很想知道对于大多数应用程序而言,同步异步是否都是值得的.
无论哪种方式,都要防止为命令处理程序提供两个接口.您必须选择一个,因为有两个单独的接口将强制您复制要应用于它们的所有装饰器并复制您的DI配置.因此要么有一个返回Task并使用输出属性的接口,要么在没有返回类型的情况下继续Task<TResut>并返回某种Void类型.
正如你能想象(你指向的文章是我的)我个人的偏好是有一个void Handle或Task Handle方法,因为用的命令,重点不是在返回值和具有返回值时,你会最终有一个重复的接口查询具有以下结构:
public interface ICommand<TResult> { }
public interface ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
Task<TResult> Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)
如果没有ICommand<TResult>接口和泛型类型约束,您将缺少编译时支持.这是我在同时解释的......在我的架构的查询方面
Jim*_*ard 10
我为此创建了一个项目 - 我最终没有拆分命令和查询,而是使用请求/响应和pub/sub - https://github.com/jbogard/MediatR
public interface IMediator
{
TResponse Send<TResponse>(IRequest<TResponse> request);
Task<TResponse> SendAsync<TResponse>(IAsyncRequest<TResponse> request);
void Publish<TNotification>(TNotification notification) where TNotification : INotification;
Task PublishAsync<TNotification>(TNotification notification) where TNotification : IAsyncNotification;
}
Run Code Online (Sandbox Code Playgroud)
对于命令不返回结果的情况,我使用了一个返回Void类型的基类(功能组的单位).这使我有一个统一的接口来发送具有响应的消息,其中null响应是显式返回值.
作为暴露命令的人,您明确选择在请求的定义中异步,而不是强迫每个人都是异步的.
小智 5
不是真正的答案,但就其价值而言,我得出了完全相同的结论和非常相似的实现。
我ICommandHandler<T>和IQueryHandler<T>回报Task,并Task<T>分别。在同步实现的情况下,我使用Task.FromResult(...). 我也有一些 *handler 装饰器(比如用于日志记录),你可以想象这些也需要改变。
现在,我决定让“一切”都可以等待,并养成了await与我的调度程序结合使用的习惯(在 ninject 内核中找到处理程序并调用它的句柄)。
我一路异步,也在我的 webapi/mvc 控制器中,除了少数例外。在那些罕见的情况下,我使用Continuewith(...)和Wait()将事物包装在同步方法中。
另一个相关的挫折是 MR 建议使用 *Async 后缀命名方法,以防它们是(废话)异步。但由于这是一个实施决定,我(目前)决定坚持Handle(...)而不是HandleAsync(...).
这绝对不是一个令人满意的结果,我也在寻找更好的解决方案。
| 归档时间: |
|
| 查看次数: |
4886 次 |
| 最近记录: |