5

我正在尝试使用以下CQRS 模式创建一个ASP.NET MVC应用程序(这是我对 CQRS 的第一次尝试)。我知道命令方面没有任何结果。在这个答案中,@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);
    }

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

4

5 回答 5

15

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

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

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

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

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

在这里,您有两个选择:

  1. 存储命令结果,等待js代码查询
  2. 使用 SignalR 之类的东西来通知事件的客户端代码。

提交登录信息后,客户端代码会取回命令ID。根据您选择的选项,您可以每隔 X 秒从 javascript 中获取登录状态(选项 1)或等待 SignalR 等机制通知(选项 2)。

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

这对于身份验证过程来说可能听起来很复杂,但确实如此,但一般来说,当您设计成功的过程和命令时,只有很少的失败命令,这实际上工作得很好。

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

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

我必须警告你,我已经看到 CQRS 被滥用并强加于一个设计为同步请求响应的进程(如 CRUD 操作),结果是系统处理模式处理事件的实际基础设施处理,然后处理实际商业逻辑。

最后,我强烈推荐 Udi Dahan 关于 CQRS 和 SOA 主题的著作,尤其是他警告滥用 os CQRS 的那些。

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

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

于 2012-09-16T01:40:50.777 回答
9

CQRS 是一个不错的概念。但它非常空洞。很多东西都可以藏在它的壳下。所以要小心你放在那里的东西。明智地思考,不要害怕回到更简单的事情上。

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

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

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

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

我记得我的两便士值得,我正在自己玩它的路上,

[编辑]

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

public class CommandValidation 
{

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

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

[/编辑]

于 2012-09-17T06:50:55.747 回答
6

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

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

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

于 2012-09-15T16:09:59.830 回答
5

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

如果您发现确实需要多于“一点时间”的命令,例如多于几秒钟,您可能会发现您实际拥有的是一个由命令启动的长时间运行的进程。在这种情况下,命令仍然是同步的,但不是在完成整个过程时返回成功,而是返回成功,它已经启动了该过程。

于 2012-09-15T19:37:54.270 回答
3

事件驱动模式不适用于 ASP.MVC。如果您正在执行大量 I/O 密集型工作以从 IO 完成端口中受益,则可以使用异步控制器。

于 2012-09-15T16:07:11.020 回答