CQRS:命令返回值

Bre*_*ias 59 architecture design-patterns command-pattern cqrs

关于命令是否应该具有返回值似乎存在无穷无尽的混淆.我想知道这种混乱是否仅仅是因为参与者没有陈述他们的背景或情况.

困惑

以下是混淆的例子......

  • Udi Dahan说命令"不会向客户端返回错误",但在同一篇文章中他展示了一个图表,其中命令确实将错误返回给客户端.

  • 微软新闻商店文章指出"命令......不会返回响应",但接着提出了一个含糊不清的警告:

随着围绕CQRS的战场经验的增长,一些实践巩固并倾向于成为最佳实践.部分地与我们刚才所说的相反......今天人们普遍认为命令处理程序和应用程序都需要知道事务操作是如何进行的.必须知道结果......

  • 吉米·博加德说" 命令总是有结果 ",但后来又付出了额外的努力来表明命令如何返回无效.

那么,命令处理程序是否返回值?

答案?

从Jimmy Bogard的" CQRS神话 "中得到启示,我认为这个问题的答案取决于你所说的程序/语境"象限":

+-------------+-------------------------+-----------------+
|             | Real-time, Synchronous  |  Queued, Async  |
+-------------+-------------------------+-----------------+
| Acceptance  | Exception/return-value* | <see below>     |
| Fulfillment | return-value            | n/a             |
+-------------+-------------------------+-----------------+
Run Code Online (Sandbox Code Playgroud)

验收(例如验证)

命令"Acceptance"主要指验证.推测验证结果必须与调用者同步,无论命令"履行"是同步还是排队.

但是,似乎许多从业者不会从命令处理程序中启动验证.从我所看到的,它要么是因为(1)他们已经找到了在应用层处理验证的绝妙方法(即ASP.NET MVC控制器通过数据注释检查有效状态)或​​者(2)架构假设命令被提交给(进程外)总线或队列.后面这些异步形式通常不提供同步验证语义或接口.

简而言之,许多设计人员可能希望命令处理程序将验证结果作为(同步)返回值提供,但它们必须遵守它们使用的异步工具的限制.

履行

关于命令的"履行",发出命令的客户端可能需要知道新创建的记录的scope_identity或者可能需要知道故障信息 - 例如"帐户透支".

在实时设置中,似乎返回值最有意义; 不应使用例外来传达与业务相关的失败结果.但是,在"排队"环境中......返回值自然没有意义.

这是所有困惑可能总结的地方:

许多(大多数?)CQRS从业者假设他们现在或将来会合并异步框架或平台(总线或队列),从而宣称命令处理程序没有返回值.但是,一些从业者无意使用此类事件驱动的构造,因此他们将支持(同步)返回值的命令处理程序.

因此,例如,我相信Jimmy Bogard提供此示例命令界面时会假设同步(请求 - 响应)上下文:

public interface ICommand<out TResult> { }

public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)

毕竟,他的Mediatr产品是一种内存工具.考虑到所有这些,我认为Jimmy 小心翼翼地花时间从命令产生void返回的原因并不是因为"命令处理程序不应该有返回值",而是因为他只是希望他的Mediator类具有一致的接口:

public interface IMediator
{
    TResponse Request<TResponse>(IQuery<TResponse> query);
    TResult Send<TResult>(ICommand<TResult> query);  //This is the signature in question.
}
Run Code Online (Sandbox Code Playgroud)

...即使并非所有命令都具有有意义的返回值.

重复并结束

我是否正确地捕捉到为什么这个主题存在混淆?有什么我想念的吗?

Ben*_*ith 16

根据Vladik Khononov 在CQRS解决复杂性的建议,建议命令处理可以返回与其结果相关的信息.

在不违反任何[CQRS]原则的情况下,命令可以安全地返回以下数据:

  • 执行结果:成功或失败;
  • 发生故障时出现错误消息或验证错误;
  • 汇总的新版本号,如果成功;

此信息将显着改善系统的用户体验,因为:

  • 您不必轮询外部源以获取命令执行结果,您可以立即使用它.验证命令和返回错误消息变得微不足道.
  • 如果要刷新显示的数据,可以使用聚合的新版本来确定视图模型是否反映执行的命令.不再显示陈旧数据.

Daniel Whittaker主张从包含此信息的命令处理程序返回" common result "对象.

  • @Greyshack 这大概就是为什么大多数 ES/CQRS 资源建议使用 GUID 作为聚合/实体 ID 的原因——客户端可以生成 ID 并将其包含在创建命令中,而不是将其留给后端来生成 ID。 (6认同)
  • 创建新对象的命令怎么样,它不应该至少返回所创建对象的 id 吗? (4认同)

Con*_*enu 5

那么,命令处理程序是否返回值?

他们不应返回业务数据,而应仅返回元数据(关于执行命令的成功或失败)。CQRSCQS提升到更高的水平。即使您违反纯粹主义者的规则并返回某些东西,您还会得到什么?在CQRS中,命令处理程序是的方法,该方法application service加载,aggregate然后在上调用方法,aggregate然后持久化aggregate。命令处理程序的目的是修改aggregate。您将不知道要返回的内容与调用者无关。每个命令处理程序调用者/客户端都想了解有关新状态的其他信息。

如果命令执行被阻止(又名同步),那么您只需要知道命令是否成功执行即可。然后,在更高的层中,将使用最适合您的需求的查询模型查询有关新应用程序状态的确切信息。

否则请考虑,如果您从命令处理程序中返回某些内容,则应承担以下两项责任:1.修改聚合状态; 2.查询某些读取模型。

关于命令验证,至少有两种类型的命令验证:

  1. 命令健全性检查,用于验证命令是否具有正确的数据(即,电子邮件地址有效);这是在命令到达集合之前,在命令处理程序(应用程序服务)或命令构造函数中完成的;
  2. 在命令到达聚合后(在聚合上调用方法之后)并且在聚合内执行的域不变性检查,并检查聚合是否可以突变为新状态。

但是,如果我们在的客户端Presentation layer(即REST端点)中进行了某种级别的提升,Application layer我们可以返回任何内容,并且不会违反规则,因为这些端点是根据用例设计的,因此您确切地知道您想要什么在每种情况下,执行命令后返回。

  • @Wirone 通常,在 CQRS 中使用 GUID,因此您*甚至在*执行命令之前就知道实体的 ID。所以,你知道 ID 和用例 =&gt; 你知道 Readmodel (3认同)
  • “他们不应该。CQRS 将 CQS 提升到更高的水平。” CQRS 是 _higher_ 级别的想法,它不关心方法。CQRS 的主要思想是避免混合写入和读取模型,但命令处理程序可以(必须)返回错误甚至产生的事件。 (3认同)
  • _“在每个用例中,您都确切地知道在执行命令后要返回什么”_ - 不,我不知道。如果客户端请求具有 CQRS 和内部命令处理程序的服务 A 重新映射一些数据并在后台调用服务 B(代表客户端),然后返回新创建资源的 ID,我将如何将此 ID 返回给客户端,如果服务 A 的命令没有返回值? (2认同)
  • @Misanthrope我同意,请再次阅读我的答案,尤其是最后一段。但是,命令处理程序(请确保:加载Aggregate的方法,调用命令方法然后持久保存Aggregate的方法)不应返回任何内容。如果需要事件,请订阅它们,HTTP处理程序(而不是Command处理程序!)可以收集这些事件并返回它们。这也适用于例外。地狱,HTTP处理程序(或Command处理程序上方的任何内容)可以查询readmodel或其他内容并返回某些状态。 (2认同)
  • “如果您需要事件,请订阅它们”在这种情况下,我认为没有理由这样做。命令处理程序,一般来说,只是将命令转换为事件。在这种情况下获取事件只是更明确的方式。 (2认同)

Mis*_*ope 5

CQRS 和 CQS 就像微服务和类分解:主要思想是一样的(“倾向于小的内聚模块”),但它们位于不同的语义层次。

CQRS 的重点是使写/读模型分离;诸如特定方法的返回值之类的低级细节完全无关紧要。

请注意以下福勒的报价

CQRS 引入的变化是将该概念模型拆分为单独的模型以进行更新和显示,在 CommandQuerySeparation 词汇表之后分别将其称为 Command 和 Query。

它是关于模型,而不是方法

命令处理程序可以返回除读取模型之外的任何内容:状态(成功/失败)、生成的事件(这是命令处理程序的主要目标,顺便说一句:为给定命令生成事件)、错误。命令处理程序经常抛出未经检查的异常,它是命令处理程序输出信号的示例。

此外,该术语的作者 Greg Young 说命令总是同步的(否则,它会变成事件):https : //groups.google.com/forum/#!topic/dddcqrs/xhJHVxDx2pM

格雷格·杨

实际上我说异步命令不存在:) 它实际上是另一个事件。