命令/查询分离命令所需的结果

时间:2012-09-15 16:01:16

标签: c# asp.net-mvc-3 design-patterns domain-driven-design cqrs

我正在尝试使用以下 CQRS模式创建 ASP.NET MVC 应用程序(这是我第一次尝试CQRS)。我理解命令端,没有任何结果。在this answer中,@Johannes Rudolph表示如果我们需要在命令端获得结果,我们可以创建事件并订阅,以解决问题。 现在,假设我们正在创建一个登录页面:

public class AuthController : Controller {

    private ICommandHandler<LoginCommand> _handler;

    public AuthController(ICommandHandler<LoginCommand> handler) {
        _handler = handler;
    }

    public ActionResult Login(LoginModel model) {
        if(ModelState.IsValid) {
            var cmd = new LoginCommand(model.Username, model.Password);
            _handler.Handle(cmd);
            // how to notify about login success or failed?
        }
        return View(model);
    }
}

如何通知登录命令成功或失败?我知道我可以在LoginCommand中创建一个事件并在上面的方法中订阅它。但是,我的订阅是一个单独的方法,不能返回(例如:)指定的视图。参见:

    public ActionResult Login(LoginModel model) {
        if(ModelState.IsValid) {
            var result = true;
            var cmd = new LoginCommand(model.Username, model.Password);
            cmd.CommandCompleted += e => { result = e; };
            _handler.Handle(cmd);
            // is this correct?
            if(result)
                // redirect to ReturnUrl
            else
                // something else
        }
        return View(model);
    }

这是真的吗?或者你有更好的主意吗?或者我错了吗?

5 个答案:

答案 0 :(得分:15)

正如其他人所说,身份验证过程对于异步命令和CQRS来说并不是一个很好的例子。实际上,同步请求/响应可能是更好的解决方案。

现在使用异步命令执行此操作(或任何其他示例),您需要发送命令并 eventualy 查询该命令的结果。

发送命令的部分是“简单”。我不会在控制器中注入处理程序,而是注入一些能够调度该命令的东西,就像允许你使用.Send()命令的总线一样。总线的实现是无关紧要的,并且可以从内存直接调度程序到处理程序到实际服务总线实现而变化。要跟踪命令,最好在构造函数中生成一个标识符(如Guid.NewGuid())并将其作为已成功分派命令的符号返回。

现在,从命令的实际处理程序中,您需要发布一个事件,包含CommandId的UserAuthenticated of AuthenticationFailed和可能相关的任何其他数据,如用户名,用户角色等。

在asp.net应用程序中,您需要为两个事件中的每个事件都有一个订阅者。

这里有两个选项:

  1. 存储命令的结果并等待js代码查询它
  2. 使用SignalR之类的东西来通知客户端代码。
  3. 提交登录信息后,客户端代码将返回命令Id。根据您选择的选项,您可以每隔X秒(选项1)从javascript汇集登录状态,或等待SignalR(选项2)等机制通知。

    有时选项1更好,有时选项2更好。如果您可以估计池化间隔的值足够快,并且在大多数情况下在第一次调用中返回结果,则选项1最好。在大多数情况下,命令被认为是成功的(如订单提交),并且您只对命令失败的罕见情况感兴趣,选项2通常是最佳选择。

    这对于身份验证过程来说可能听起来很复杂,而且确实如此,但一般来说,当您设计流程和命令以获得成功时,并且只有非常少的失败命令,这实际上非常有效。

    对于其他情况,您可能需要使用两个选项的组合 - 使用选项2获取命令已执行的通知,然后查询视图模型以获取相关数据。

    还有一个附加选项涉及和AsyncController,您可以在其中发送命令并异步等待处理程序接收消息。我没有使用异步控制器,所以以下只是一个意见,但我只是觉得,如果你需要使用异步控制器,你可能会更好的使用异步控制器的请求 - 响应实现,以尽量减少影响在网络服务器上。

    我必须警告你,我已经看到CQRS被滥用并强制执行一个旨在同步请求 - 响应的进程(如CRUD操作),结果是系统处理模式与事件的实际基础结构处理然后与实际的业务逻辑。

    最后,我强烈推荐Udi Dahan关于CQRS和SOA主题的着作,特别是那些他警告滥用os CQRS的文章。

    希望这可以帮助您进入CQRS的伟大世界:)

    (这最终比预期的要长很多)

答案 1 :(得分:9)

CQRS是一个很好的概念。但它很空洞。很多事情都可以归根结底。所以要小心你放在那里。明智地思考,尽量不要害怕回到更简单的事情。

根据您的问题,处理命令可以是同步或异步。我不建议将您的命令发送到总线作为您的基本架构。在大多数情况下,以同步方式处理命令要容易得多。 通常你发送一个应该有效的命令。 (例如:StartDate不得大于结束日期)。然后,您将进入命令处理程序,在此处可以在应用程序层的上下文中验证命令的有效性。 (例如:应该存在的聚合根“House”是否确实存在?)最后,您的域处理此命令并检查其在域上下文中的有效性(例如AR“House”可能执行复杂的事情,您的命令可能成为......)的一部分。

您希望从此命令验证流程中得到什么?这取决于你。您可能有兴趣知道某些验证未能警告您的用户。 或者抛出异常,因为任何客户端都不应该处理这种情况。

如果你选择异步,那意味着UI不会意识到执行命令时会出现任何问题。可能会工作,但在大多数情况下,您的客户填写表格并愿意知道其行动是否失败。

就我而言,我越是探索这些模式(或者你决定称之为它们的任何模式),我认为异步应该被视为异常。您应该最好地完成命令处理和事件投影的完全同步。和异步你需要异步特征的部分。 你可以开始完全同步,你可以看到这种方法不适合,好吧,一切都准备好把东西放到总线上,然后去异步。

我记得,我的两个便士值得我自己去玩它,

[编辑]

通常,执行我的命令会返回:

public class CommandValidation 
{

    public List<ValidationMessage> Messages { get; private set; }
}

CQRS告诉我从我的方法中返回无效。每个聚合都有一个命令验证属性,该属性在执行命令期间被填充。在执行结束时,我可以将其返回给客户端。

[/编辑]

答案 2 :(得分:6)

您为什么要使用CQRS建模用户管理和身份验证?这是一个解决的问题,不需要在这里重新发明轮子。

此外:身份验证是一个包含多个有界上下文并且非常适合CQRS的问题。 CQRS旨在在有界上下文中实现,而不是在应用程序范围内实现。使用可以工作的登录机制,将您的建模工作集中在域上,而不是基础架构上。

用Rob Ashton的话来说:如果你的解决方案比你的问题更复杂,你可能做错了。

答案 3 :(得分:5)

如其他地方所述,CQRS可能不适合简单的身份验证和用户管理。但是如果您确实选择使用它,那么问题的核心可能就是您试图使命令异步。我认为从返回成功或失败的同步命令开始通常会更好。 (注意:这仍然可以涉及任何给定提供程序中的“异步”代码,例如AJAX或TPL代码,但从概念上讲,调用者正在等待并期望执行命令会导致成功或失败。)

如果您发现命令确实需要的时间超过“一会儿”,例如超过几秒钟,你可能会发现你实际拥有的是一个长期运行的过程,它是用命令启动的。在这种情况下,命令仍然是同步的,但不是在完成整个过程后返回成功,而是返回启动过程的成功。

答案 4 :(得分:3)

事件驱动模式与ASP.MVC不兼容。如果您正在进行大量I / O密集型工作以便从IO完成端口中受益,则可以使用asynchronous controllers