【问题标题】:Command Validation in DDD with CQRS使用 CQRS 在 DDD 中进行命令验证
【发布时间】:2025-11-21 15:05:01
【问题描述】:

我正在学习 DDD 并使用 CQRS 模式。我不明白如何在不读取数据存储的情况下验证命令处理程序中的业务规则。

例如,Chris 想给 Ashley 一份礼物。

命令可能是 GiveGiftCommand。

我会在什么时候确认 Chris 确实拥有他想要赠送的礼物?如果不从数据库中读取,我将如何做到这一点?

【问题讨论】:

  • 如果礼物具有“所有权”意识,并且礼物会“记住”克里斯是所有者,那么在gift.Give(giver: chris, receiver: ashley) 上,礼物可以验证chris(指代的变量Chris 作为实体或值对象)确实是所有者。
  • @YvesReynhout 以及 Gift 如何知道所有者 Chris 的真实存在? :)
  • 在礼物出现的时候,把送礼者当作工厂。显然,您只能确定系统是否认识注册为 Chris 的人。

标签: validation domain-driven-design cqrs


【解决方案1】:

关于命令处理程序中的验证有不同的看法和意见。

命令可以被拒绝,如果命令无效,我们可以对命令说No

通常,您会在 UI 上进行验证,并且可能会在命令处理程序中重复(有些人也倾向于将其放在域中)。然后,命令处理程序可以运行可能在实体外部发生的简单验证,例如格式正确的数据、是否存在预期值等。

另一方面,业务逻辑不应在命令处理程序中。它应该在您的域中。

所以我认为根本问题是......

我应该从命令处理程序查询读取端吗?

我会说不。不要在命令处理程序或域逻辑中使用读取模型。但是您始终可以从客户端查询您的读取模型,以获取您的命令所需的数据并验证该命令。您将查询客户端的读取端以检查 Chris 是否真的拥有他想要赠送的礼物。当然,涉及读取模型的验证很可能最终是一致的,这当然是在命令处理程序内部从聚合中拒绝命令的另一个原因。

有些人不同意,如果您要求您的命令包含处理程序验证命令所需的数据,那么您永远无法更改处理程序/域中的验证逻辑而不影响客户端。这向客户暴露了太多的领域知识,与客户只想表达意图的事实背道而驰。因此,他们倾向于为您的命令处理程序提供一个GiftService 接口(这是通用语言的一部分),然后根据需要实现该接口 - 这可能包括查询读取端。

我认为客户端应该始终假设它发出的命令会成功。不需要调用读取端来验证命令。获得两个相互矛盾的命令的可能性很小(用户使用相同的电子邮件地址创建帐户)。然后,您应该有办法发出纠正措施,例如 Saga/Process Manager。因此,如果命令可以被验证并且一开始就没有被分派,那么采取纠正措施的问题会更小。

【讨论】:

  • 我没有做过很多这种类型的事情......但我会认为读取模型只是域中包含的数据的不同表示形式。域不应该能够使用自己的数据存储来处理命令吗?任何错误都可能像任何其他事件/数据一样波及整个系统。
  • 域模型是面向命令的。如果分布在多个聚合之间,它的结构并不总是允许有效地查询它自己的数据。它会以人为的方式耦合聚合根,这不会有效。
  • 读取模型只是真相的投影,可以在真实真相的背后。因此,在询问读取模型时,您必须牢记这一点。
  • 另外从 cqrs google group 的进一步讨论中我学到了一些东西。命令处理程序应该调用聚合根上的方法来应用命令。聚合根将在初始化时加载事件流,最终结果将是聚合的当前状态。然后,您可以使用当前状态执行一些业务规则。
  • 你说的是什么服务?我假设域服务。就我而言,我将命令处理程序视为应用程序服务,因此没有业务逻辑。只是编排用例。
【解决方案2】:

这取决于操作是否异步,即用户是否期望立即得到响应。礼物所有权基本上是一项安全功能,可以在调用实际服务或发送 GiveGiftCommand 之前作为“准备”操作完成。

您可以执行的唯一命令验证是确保它包含所需格式的数据(UI 验证)并且用户有权执行该操作。但是在发送命令后,由域决定是否遵守其他业务约束。

如果用户期望一些即时反馈,您实际上可以“等待”直到命令完成,为此您可以使用 an approach where a command handler can provide a result to the sender using a mediator 。但这意味着至少有一些命令是立即执行的,而在您的应用程序中可能并非如此。但是,如果您只想返回消息错误而不是实现补偿和其他内容,这是最简单的方法。有些用例很简单。

关于命令处理程序和业务逻辑,我不同意 Tomasz Jaskuλa 。命令处理程序是一个函数,一个技术细节。您可以将业务逻辑放在命令处理程序或静态函数中,没关系。消息及其处理程序是可用于实现功能的基础设施组件。例如,在应用程序中,您可以拥有领域事件、应用程序事件等。它们都是事件,即通知某些事情发生了变化,您可以拥有驻留在域或其他地方的事件处理程序。

没有规则阻止您从数据库“读取”,但至少理论上读取模型是陈旧的。然而,在 99% 的情况下,这可能不是一个问题。对于剩下的 1%,您需要非常具体的解决方案。

【讨论】:

    【解决方案3】:

    我刚刚问了我一个知识渊博的朋友同样的问题,他的回答是可以在命令处理程序中进行此验证(在我的情况下,在解释命令并将事件写入日志的 akka 持久性参与者中) )。

    但是,如果出于性能原因无法做到这一点(因为在持久性 Actor 内处理验证会阻塞该 Actor,这将成为整个应用程序的可扩展性瓶颈),则可以使用乐观锁定 (OCC)。

    换句话说,验证可以在其他参与者中执行(我们称之为验证参与者——它不在持久参与者中),这不会阻塞持久参与者,但可能会发生用于的数据当验证在验证器参与者中运行时,持久参与者中的验证已更改)。

    如果验证器参与者返回一个 OK 并且已用于验证的所有数据在持久参与者中仍然是相同的(具有与 OCC 中相同的版本)命令到达命令处理程序(持久化actor)然后命令被持久化actor接受,否则需要重新提交验证以重新评估给验证器actor。

    【讨论】:

      最近更新 更多