ICommandHandler / IQueryHandler与异步/的await [英] ICommandHandler/IQueryHandler with async/await

查看:236
本文介绍了ICommandHandler / IQueryHandler与异步/的await的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我去建议的解决方案的一个变体;保持所有 ICommandHandler IQueryHandler 取值可能aynchronous和同步的情况下返回一个解决的任务。不过,我不希望使用 Task.FromResult(...)所有的地方,所以我定义了一个方便的扩展方法:

I went with a variant of the suggested solution; keeping all ICommandHandlers and IQueryHandlers potentially aynchronous and returning a resolved task in synchronous cases. Still, I don't want to use Task.FromResult(...) all over the place so I defined an extension method for convenience:

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#是不是哈斯克尔 ...但8-)。真的闻起来像的箭头的应用程序。不管怎么说,希望这可以帮助任何人。现在我原来的问题: - )

It's a pity that C# isn't Haskell ... yet 8-). Really smells like an application of Arrows. Anyway, hope this helps anyone. Now to my original question :-)

你好!

有关的一个项目,我目前正在设计在C#中(.NET4.5,C#5.0,ASP.NET MVC4)应用程序架构。带着这个问题,我希望得到一些问题,我偶然发现了试图将异步/的await 一些意见。注:这是一个相当漫长的之一: - )

For a project I'm currently designing an application architecture in C# (.NET4.5, C#5.0, ASP.NET MVC4). With this question I hope to get some opinions about some issues I stumbled upon trying to incorporate async/await. Note: this is quite a lengthy one :-)

我的解决方案的结构是这样的:

My solution structure looks like this:


  • MyCompany.Contract (命令/查询和常用接口)

  • MyCompany.MyProject (包含业务逻辑和命令/查询处理程序)

  • MyCompany.MyProject.Web (MVC的Web前台)

  • MyCompany.Contract (Commands/Queries and common interfaces)
  • MyCompany.MyProject (Contains the business logic and command/query handlers)
  • MyCompany.MyProject.Web (The MVC web frontend)

我在维护结构和命令查询的分离阅读后,发现这些职位非常有帮助的:

I read up on maintainable architecture and Command-Query-Separation and found these posts very helpful:

  • Meanwhile on the query side of my architecture
  • Meanwhile on the command side of my architecture
  • Writing highly maintainable WCF services

到目前为止,我已经得到了我周围的 ICommandHandler / IQueryHandler 概念和依赖注入(我的头使用 SimpleInjector - 这是真的死了简单)。

So far I've got my head around the ICommandHandler/IQueryHandler concepts and dependency injection (I'm using SimpleInjector - it's really dead simple).

以上物品的方法建议使用波苏斯作为命令/查询,并介绍这些调度员为以下处理程序接口的实现:

The approach of the articles above suggests using POCOs as commands/queries and describes dispatchers of these as implementations of the following handler interfaces:

interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}

interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

在一个MVC控制器你会使用这个如下:

In a MVC Controller you'd use this as follows:

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
    }
}

我的一些关于这种方法的观察:

Some of my observations concerning this approach:


  1. 在纯 CQS 只查询应返回值,同时命令应该是,以及只有命令。在现实中,它是更方便的命令,而不是返回发出命令,后来做了命令应摆在首位(例如数据库ID等)已经返回的东西查询的值。这就是为什么笔者建议把的返回值进入命令POCO。

  2. 这不是很明显的东西是从命令返回的,其实它的看起来的类似命令是一个火,忘记的东西种类直到你最终遇到怪异的结果财产被处理程序后访问已运行加命令现在知道它的结果

  3. 的处理程序有无的是同步的这个工作 - 查询和命令。事实证明用C#5.0你可以注入异步/的await 处理器供电用你喜欢的DI容器的帮助,但是编译器不知道在编译时所以MVC处理程序将抛出异​​常告诉你,之前的所有异步任务执行完该方法返回惨遭失败。

  1. In pure CQS only queries should return values while commands should be, well only commands. In reality it is more convenient for commands to return values instead of issuing the command and later doing a query for the thing the command should have returned in the first place (e.g. database ids or the like). That's why the author suggested putting a return value into the command POCO.
  2. It is not very obvious what is returned from a command, in fact it looks like the command is a fire and forget type of thing until you eventually encounter the weird result property being accessed after the handler has run plus the command now knows about it's result
  3. The handlers have to be synchronous for this to work - queries as well as commands. As it turns out with C#5.0 you can inject async/await powered handlers with the help of your favorite DI container, but the compiler doesn't know about that at compile time so the MVC handler will fail miserably with an exception telling you that the method returned before all asynchronous tasks finished executing.

当然,你可以标记MVC处理程序异步,这是这个问题是关于什么的。

Of course you can mark the MVC handler as async and this is what this question is about.

我想到了给定的方法,并更改了接口,以解决问题,我加1和2的 ICommandHandler ,有一个明确的结果类型 - 只是像 IQueryHandler 。这仍然违反CQS但至少它是平原明显,这些命令将返回某种价值与不与结果属性杂乱命令对象的额外好处:

I thought about the given approach and made changes to the interfaces to address issues 1. and 2. in that I added an ICommandHandler that has an explicit result type - just like the IQueryHandler. This still violates CQS but at least it is plain obvious that these commands return some sort of value with the additional benefit of not having to clutter the command object with a result property:

interface ICommandHandler<TCommand, TResult>
{
    TResult Handle(TCommand command);
}

当然有人会说,当你对命令和查询相同的接口何必呢?但我认为这是值得不同命名它们 - 只是看起来更清洁,以我的眼睛。

Naturally one could argue that when you have the same interface for commands and queries why bother? But I think it's worth naming them differently - just looks cleaner to my eyes.

这时我想到辛苦了第三期的眼前......我的一些命令/查询处理的需要是异步的(如发出的WebRequest 来为另一个Web服务验证)别人不一样。所以,我想这将是最好的从地上爬起来为设计我处理异步/的await - 这当然是泡到MVC处理程序,即使对于那些事实上同步处理

Then I thought hard of the 3rd issue at hand ... some of my command/query handlers need to be asynchronous (e.g. issuing a WebRequest to another web service for authentication) others don't. So I figured it would be best to design my handlers from the ground up for async/await - which of course bubbles up to the MVC handlers even for handlers that are in fact synchronous:

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:

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
    }
}

虽然这解决了我的问题 - 明显的返回值,所有的处理程序可以异步 - 它伤害了我的大脑把异步上不只是因为一个异步啄。有几个缺点我与此看到:

Although this solves my problems - obvious return values, all handlers can be async - it hurts my brain to put async on a thingy that isn't async just because. There are several drawbacks I see with this:


  • 的处理程序接口不是因为我想他们是整洁 - 在任务&LT; ...&GT; thingys到我的眼睛是很冗长,一见钟情模糊处理事实上,我只想从查询/命令
  • 返回的东西
  • 编译器会警告你没有一个合适的等待同步处理程序实现(我希望能够编译在我的发布警告视为错误) - 你可以用一个编译覆盖此...是啊...好...

  • 我可以忽略这些情况下的异步关键字使编译器高兴,但为了实现处理器的接口,你就必须返回某种<$ C $的C>工作明确 - 这是pretty丑

  • 我可以提供处理器接口的同步和异步版本(或把他们都在一个界面腹胀的实现),但我的理解是,理想情况下,处理器的消费者不应该知道这样一个事实:命令/查询处理程序是同步或异步,因为这是一个横切关注点。如果我需要什么让一个以前同步命令异步?我不得不更改处理程序通过code可能打破我的方式语义的每一位消费者。

  • 在另一方面的可能的-async-处理的方法甚至会再给我用我的DI容器的帮助装饰他们改变同步处理是异步的能力

  • The handler interfaces are not as neat as I wanted them to be - the Task<...> thingys to my eyes are very verbose and at first sight obfuscate the fact that I only want to return something from a query/command
  • The compiler warns you about not having an appropriate await within synchronous handler implementations (I want to be able to compile my Release with Warnings as Errors) - you can overwrite this with a pragma ... yeah ... well ...
  • I could omit the async keyword in these cases to make the compiler happy but in order to implement the handler interface you would have to return some sort of Task explicitly - that's pretty ugly
  • I could supply synchronous and asynchronous versions of the handler interfaces (or put all of them in one interface bloating the implementation) but my understanding is that, ideally, the consumer of a handler shouldn't be aware of the fact that a command/query handler is sync or async as this is a cross cutting concern. What if I need to make a formerly synchronous command async? I'd have to change every consumer of the handler potentially breaking semantics on my way through the code.
  • On the other hand the potentially-async-handlers-approach would even give me the ability to change sync handlers to be async by decorating them with the help of my DI container

现在我没有看到的的解决这个...我无所适从。

Right now I don't see a best solution to this ... I'm at a loss.

有类似的问题和优雅的解决方案我没有想到的?任何

Anyone having a similar problem and an elegant solution I didn't think of?

推荐答案

异步和等待不与传统的OOP完美混合。我对这个问题的博客系列;您可能会发现异步接口特别很有帮助后(虽然我不盖你还没有已发现的任何东西)。

Async and await don't mix perfectly with traditional OOP. I have a blog series on the subject; you may find the post on async interfaces helpful in particular (though I don't cover anything you haven't already discovered).

围绕的设计问题异步非常相似,周围的的IDisposable 的那些;这是一个重大更改为的IDisposable 添加到界面,所以你需要知道是否有可能实现可能的曾经的是一次性的(实现细节)。与 A并行存在问题异步;你需要知道是否有可能实现可能的曾经的是异步的(实现细节)。

The design problems around async are extremely similar to the ones around IDisposable; it's a breaking change to add IDisposable to an interface, so you need to know whether any possible implementation may ever be disposable (an implementation detail). A parallel problem exists with async; you need to know whether any possible implementation may ever be asynchronous (an implementation detail).

由于这些原因,我认为工作 -returning接口方式为可能异步的方法,就像一个接口从 IDisposable的<继承/ code>意味着它可能拥有的资源。

For these reasons, I view Task-returning methods on an interface as "possibly asynchronous" methods, just like an interface inheriting from IDisposable means it "possibly owns resources."

我知道的最好的方法是:

The best approach I know of is:


  • 定义是可能异步与异步签名的方法(返回工作 / 任务&LT; T&GT;

  • 返回 Task.FromResult(...)同步实现。这是不是一个更恰当异步没有等待

  • Define any methods that are possibly-asynchronous with an asynchronous signature (returning Task/Task<T>).
  • Return Task.FromResult(...) for synchronous implementations. This is more proper than an async without an await.

这方法几乎完全你已经做什么。更理想的解决方案可能会存在一个纯粹的功能性语言,但我没有看到一个用于C#。

This approach is almost exactly what you're already doing. A more ideal solution may exist for a purely functional language, but I don't see one for C#.

这篇关于ICommandHandler / IQueryHandler与异步/的await的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆