需要命令/查询分离命令端结果



我正在尝试创建一个ASP.NET MVC应用程序,该应用程序具有以下CQRS模式(这是我第一次体验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中创建一个事件,并用上面的方法订阅它。但是,我的subscription是一个单独的方法,不能返回(例如:)指定的视图。参见:

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

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

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

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

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

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

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

这里有两个选项:

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

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

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

对于身份验证过程来说,这听起来可能很复杂,事实确实如此,但通常情况下,当您为成功设计过程和命令,并且只有极少数失败的命令时,这实际上非常有效。

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

还有一个涉及和AsyncController的附加选项,您可以在其中发送命令并异步等待处理程序接收消息。我没有使用异步控制器,所以下面只是一个观点,但我只是觉得,如果你需要使用异步控制器的话,你可能会更好地使用请求-响应实现,该实现使用异步控制器来最大限度地减少对Web服务器的影响。

我必须警告您,我已经看到CQRS被滥用并强制用于一个设计为同步请求-响应的过程(如CRUD操作),其结果是系统处理事件的实际基础结构处理,然后处理实际业务逻辑。

最后,我强烈推荐Udi Dahan关于CQRS和SOA主题的文章,尤其是他警告CQRS被滥用的文章。

希望这能帮助你进入CQRS的伟大世界:)

(这比预期的要长得多)

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

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

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

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

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

据我回忆,我的两便士价值,我正在自己玩它的路上,

[编辑]

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

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

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

[/Edit]

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

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

用罗伯·阿什顿的话来说:如果你的解决方案比你的问题更复杂,那么你可能做错了什么。

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

如果你发现命令确实需要"一段时间",例如几秒钟以上,你可能会发现你实际上拥有的是一个由命令启动的长时间运行的过程。在这种情况下,命令仍然是同步的,但它不是在完成整个过程时返回成功,而是在启动过程时返回失败。

事件驱动模式不能很好地与ASP.MVC配合使用。如果您要做大量I/O密集型工作,以便从IO完成端口中获益,则可以使用异步控制器。

最新更新