【问题标题】:In CQRS, how do you build the response when creating an entity?在 CQRS 中,创建实体时如何构建响应?
【发布时间】:2017-01-26 06:50:10
【问题描述】:

如果使用 CQRS 并创建一个实体,并且其某些属性的值是在其构造函数的一部分中生成的(例如,status 属性的默认 active 值,或 createdAt 的当前日期时间) ,如果您的命令处理程序无法返回值,您如何将其作为响应的一部分?

【问题讨论】:

  • 为什么你的命令处理程序不能返回值?
  • 因为CQS原则
  • @ConstantinGALBENU 不完全准确(见我的回答)

标签: domain-driven-design cqrs


【解决方案1】:

严格来说,我不认为 CQRS 有关于命令处理程序不返回值的精确硬性规则。 Greg Young 甚至将mentions Martin Fowler 的stack.pop() 轶事作为该规则的有效反例。

CQS - 命令查询 Separation,CQRS 所基于的 - Bertrand Meyer 确实有该规则,但它发生在不同的上下文中并且有例外,其中一个可能对手头的问题。

CQS 关于对象的原因以及例程(执行上下文)可以给它们的指令种类。发出命令时,不需要返回值,因为例程已经引用了对象,并且可以随时查询它作为命令的后续。

不过,Meyer 在 CQS 中做出的一个重要区别是发送到已知现有对象的命令与创建对象并返回它的指令之间的区别。

创建对象的函数

在我们进一步研究之前,需要澄清一个技术点 命令-查询分离原则的后果:我们应该 将对象创建视为副作用?

答案是肯定的,正如我们已经看到的,如果创建的目标是一个属性a:在这种情况下, 指示!! a 更改对象字段的值。这 如果目标是例程的本地实体,答案是否定的。但是什么 如果目标是函数本身的结果,如 !!结果或 更一般的形式!结果.make(...)?

这样的创建指令不必被视为副作用。它 不会改变任何现有对象,因此不会危及 参照透明性(至少如果我们假设有足够的 内存来分配我们需要的所有对象)。从数学上 我们可以假设所有感兴趣的对象,因为 过去、现在和未来的所有时间,都已铭刻在伟大的 物品之书;创建指令只是获得其中之一的一种方式 它们,但它本身并不会改变环境中的任何东西。 创建的函数很常见,并且合法, 初始化并返回这样一个对象。

(在面向对象的软件构建中,第 754 页)

在本书的其他地方,Meyer 将这种函数定义为 creator 函数。

由于 CQRS 是 CQS 的扩展,并且坚持 [Commands and Queries] 应该是纯粹的观点,我倾向于说 CQS 的例外情况在 CQRS 中也是正确的。 p>

此外,CQS 和 CQRS 之间的主要区别之一是将命令和查询具体化为它们自己的对象。 在 CQRS 中,有一个额外的间接级别,“例程”没有对域对象的直接引用。对象查找和修改委托给命令处理程序。它削弱了,IMO,使“命令不返回任何内容”规则成为可能的原始原因之一,因为上下文现在无法自行检查操作的结果 - 它基本上处于高位和干燥状态,直到其他一些对象允许它知道结果。

【讨论】:

  • 同意这里没有硬性规定。我已经完成了同步/返回值命令和异步命令 - 以 IMO 两种方式进行权衡。此处blog.ploeh.dk/2014/08/11/cqs-versus-server-generated-ids 也进行了有趣的讨论,讨论了代码边界与域内的不同规则。
  • 确实很有趣,谢谢。 M Seemann 似乎与 Meyer 的书不同,他说创建实体“会改变系统的状态”。
【解决方案2】:

您需要在创建实体之前创建 guid,然后使用此 guid 对其进行查询。这样你的命令处理程序总是返回 void。

    [HttpPost]
    public ActionResult Add(string name)
    {
        Guid guid = Guid.NewGuid();
        _bus.Send(new CreateInventoryItem(guid, name));
        return RedirectToAction("Item", new { id = guid});
    }


    public ActionResult Item(Guid id)
    {
        ViewData.Model = _readmodel.GetInventoryItemDetailsByGuid(id);
        return View();
    }

【讨论】:

  • +1。我遵循相同的通用方法,尽管我的命令返回包含 ack/nack 的特定命令状态对象,以便我可以立即对格式错误/无效的命令进行故障排除。那时没有域验证,因为它不属于那里。诚然,这种方法最适合最终的一致性,但它确实可以防止域重新回到它不属于的地方(例如应用程序层)。
【解决方案3】:

根据我对 CQRS 的理解,不能查询聚合,命令处理程序不能返回任何值。询问聚合的唯一允许方法是侦听引发的事件。如果更改从事件同步反映到读取模型,您可以通过简单地查询读取模型来做到这一点。

如果读取模型的更改是异步的,事情会变得复杂,但存在解决方案。

注意:我的回答中的“命令处理程序”是聚合上的方法,而不是某些应用层服务。

【讨论】:

    【解决方案4】:

    一些想法:

    1. 让您的命令处理程序返回值。这是最简单的选项 - 只需返回在实体内部创建的内容。但是,对于 CQRS 中是否“允许”这种做法存在一些分歧。
    2. 首选方法是创建您的默认值(即 id)并将它们传递给您的命令 - 例如,在 Add 方法中 https://github.com/gregoryyoung/m-r/blob/master/CQRSGui/Controllers/HomeController.cs,创建一个 Guid 并将其传递给 CreateInventoryItem 命令 - 这可以在响应。如果你有很多东西要传递,这可能会变得很丑。
    3. 如果您不能执行 1 或 2,您可以尝试采用一些异步方式来处理此问题,但您尚未说明您的用例是什么,因此很难给出建议。如果您使用某种套接字技术,则可以在立即返回的情况下执行同步异步样式,然后在创建实体后将一些值推回客户端。您还可以有某种工作流程,您可以在其中接受命令,然后对正在创建的事物进行轮询/查询

    【讨论】:

    • 由于 CQS,命令不得返回任何内容。如果一个命令不被接受,那么它必须抛出一个异常。如果它被接受,那么它不能抛出任何东西。这就是你知道它被聚合接受的方式。
    • 最初的问题是如何从自动创建的字段中获取值,而不是如何处理错误。我不认为创建某些内容并返回 ID(例如)的同步命令是 CQS 方面的查询。你仍然在指挥方面。你还在变异状态。此外,我不会同意您的断言,即必须为未接受的命令引发异常 - 您同样可以返回错误和原因,具体取决于您正在编程的语言的约定
    • 如果命令方法和查询方法都返回一些东西,你如何发现它们之间的区别?我告诉你怎么做:你阅读整个方法体或与方法相关的一些注释。这就是 CQS 存在的原因:如果您阅读方法的签名,您会立即看到该方法是查询或命令。这是更简洁的代码。
    • 请参阅下面有关 CQRS 与 CQS 的答案。特别是来自 Object Oriented Software Construction 的引述:“创建、初始化和返回这样一个对象的函数是常见且合法的。”在讨论创建对象时。
    • 我同意。我们不是机器人,我们必须在每种情况下决定什么是最好的方法,但我尽量遵循最佳做法。
    【解决方案5】:

    我最终创建了第三种类型:CommandQuery。只要有可能,一切都会被推送到命令或查询中,但是当您遇到运行命令会产生上述数据的场景时,只需转向命令查询。这样,您就知道这些是特殊情况,例如您需要来自创建的自动 ID,或者您需要从堆栈弹出中返回一些东西,并且您有一个清晰/简单的方法来处理这个问题,而不会产生额外的开销来创建一些随机虚拟 guid 或依赖事件(当您在 Web 请求上下文中时很难)会导致。在业务环境中,您可以作为一个团队讨论是否真的需要使用 CommandQuery。

    【讨论】:

      猜你喜欢
      • 2015-04-03
      • 2015-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多