使用 CQRS 在 DDD 中进行命令验证
Command Validation in DDD with CQRS
我正在学习 DDD 并使用 CQRS 模式。我不明白如何在不读取数据存储的情况下在命令处理程序中验证业务规则。
例如,Chris 想送给 Ashley 一件礼物。
该命令可能是 GiveGiftCommand。
我什么时候可以确认克里斯确实拥有他想要赠送的礼物?如果不从数据库中读取,我将如何做到这一点?
关于命令处理程序中的验证存在不同的观点和意见。
命令可以拒绝,如果命令无效,我们可以对命令说否。
通常,您会在 UI 上进行验证,并且可能会在命令处理程序中重复(有些人也倾向于将其放在域中)。命令处理程序然后可以 运行 可以在实体外部发生的简单验证,例如数据格式是否正确,是否存在预期值等。
另一方面,业务逻辑不应该在命令处理程序中。它应该在您的域中。
所以我认为潜在的问题是...
我应该从命令处理程序查询读取端吗?
我会说不。不要在命令处理程序或域逻辑中使用读取模型。但是您 始终可以从客户端查询您的读取模型 以获取命令所需的数据并验证命令。您可以查询客户端的读取端,以检查 Chris 是否真的拥有他想要赠送的礼物。当然,涉及读取模型的验证可能最终是一致的,这当然是命令处理程序内部的聚合可能拒绝命令的另一个原因。
有些人不同意这样的说法,如果您要求您的命令包含处理程序验证您的命令所需的数据,那么您永远无法在不影响客户端的情况下更改 handler/domain 中的验证逻辑。这向客户暴露了太多的领域知识,并且与客户只想表达意图的事实背道而驰。因此,他们倾向于为您的命令处理程序提供一个 GiftService
接口(这是通用语言的一部分),然后根据需要实现该接口——这可能包括查询读取端。
我认为 客户端应该总是假设它发出的命令会成功。不需要调用读取端来验证命令。获得两个相互矛盾的命令的可能性很小(用户使用相同的电子邮件地址创建帐户)。然后你应该有办法发出纠正措施,例如 Saga/Process 经理。因此,如果命令已经过验证而不是一开始就派发,那么采取纠正措施的问题就会少一些。
这取决于操作是否异步,即用户是否期望一些即时响应。礼物所有权基本上是一项安全功能,可以在调用实际服务或发送 GiveGiftCommand 之前作为 'prep' 操作完成。
您唯一可以执行的命令验证是确保它包含所需格式的数据(UI 验证)并且用户有权执行该操作。但在发送命令后,由域决定是否遵守其他业务约束。
如果用户希望得到一些即时反馈,实际上您可以 'wait' 直到命令完成,为此您可以使用 an approach where a command handler can provide a result to the sender using a mediator 。但这意味着至少有一些命令是立即执行的,而在您的应用程序中可能并非如此。但是,如果您只想 return 消息错误而不是实施补偿和其他内容,这是最简单的方法。有些用例很简单。
关于命令处理程序和业务逻辑,我不同意 Tomasz Jaskuλa 的观点。命令处理程序是一个函数,一个技术细节。您可以将业务逻辑放在命令处理程序或静态函数中,这都没有关系。消息及其处理程序是可用于实现 功能的基础结构组件。例如,在应用程序中,您可以拥有领域事件、应用程序事件等。它们都是事件,即发生变化的通知,您可以拥有驻留在域中或其他地方的事件处理程序。
没有规则阻止您从数据库中 'read',但至少理论上读取模型是陈旧的。然而,在 99% 的情况下,这可能不是这样的问题。对于剩下的 1%,您需要非常具体的解决方案。
我刚刚问了我一个知识渊博的朋友完全相同的问题,他的回答是可以在命令处理程序中进行此验证(在我的例子中,在解释命令并将事件写入的 akka 持久化角色中)日记)。
但是,如果出于性能原因这不可能(因为在持久性 actor 内部处理验证会阻塞 actor,这将成为整个应用程序的可伸缩性瓶颈),则可以使用乐观锁定 (OCC)。
换句话说,验证可以在另一个参与者中执行(我们称之为验证者参与者——它不在持久性参与者中),这不会阻止持久性参与者,但可能会发生数据是用于验证的持久性 actor 已更改,而验证器 actor 中的验证为 运行)。
如果验证器 actor returns 具有 OK 并且所有用于验证的数据在持久性 actor 中仍然相同(具有相同的版本 - 与 OCC 中的版本相同)在命令到达命令处理程序(持久化角色)时,命令被持久化角色接受,否则需要重新提交验证以重新评估给验证者角色。
我正在学习 DDD 并使用 CQRS 模式。我不明白如何在不读取数据存储的情况下在命令处理程序中验证业务规则。
例如,Chris 想送给 Ashley 一件礼物。
该命令可能是 GiveGiftCommand。
我什么时候可以确认克里斯确实拥有他想要赠送的礼物?如果不从数据库中读取,我将如何做到这一点?
关于命令处理程序中的验证存在不同的观点和意见。
命令可以拒绝,如果命令无效,我们可以对命令说否。
通常,您会在 UI 上进行验证,并且可能会在命令处理程序中重复(有些人也倾向于将其放在域中)。命令处理程序然后可以 运行 可以在实体外部发生的简单验证,例如数据格式是否正确,是否存在预期值等。
另一方面,业务逻辑不应该在命令处理程序中。它应该在您的域中。
所以我认为潜在的问题是...
我应该从命令处理程序查询读取端吗?
我会说不。不要在命令处理程序或域逻辑中使用读取模型。但是您 始终可以从客户端查询您的读取模型 以获取命令所需的数据并验证命令。您可以查询客户端的读取端,以检查 Chris 是否真的拥有他想要赠送的礼物。当然,涉及读取模型的验证可能最终是一致的,这当然是命令处理程序内部的聚合可能拒绝命令的另一个原因。
有些人不同意这样的说法,如果您要求您的命令包含处理程序验证您的命令所需的数据,那么您永远无法在不影响客户端的情况下更改 handler/domain 中的验证逻辑。这向客户暴露了太多的领域知识,并且与客户只想表达意图的事实背道而驰。因此,他们倾向于为您的命令处理程序提供一个 GiftService
接口(这是通用语言的一部分),然后根据需要实现该接口——这可能包括查询读取端。
我认为 客户端应该总是假设它发出的命令会成功。不需要调用读取端来验证命令。获得两个相互矛盾的命令的可能性很小(用户使用相同的电子邮件地址创建帐户)。然后你应该有办法发出纠正措施,例如 Saga/Process 经理。因此,如果命令已经过验证而不是一开始就派发,那么采取纠正措施的问题就会少一些。
这取决于操作是否异步,即用户是否期望一些即时响应。礼物所有权基本上是一项安全功能,可以在调用实际服务或发送 GiveGiftCommand 之前作为 'prep' 操作完成。
您唯一可以执行的命令验证是确保它包含所需格式的数据(UI 验证)并且用户有权执行该操作。但在发送命令后,由域决定是否遵守其他业务约束。
如果用户希望得到一些即时反馈,实际上您可以 'wait' 直到命令完成,为此您可以使用 an approach where a command handler can provide a result to the sender using a mediator 。但这意味着至少有一些命令是立即执行的,而在您的应用程序中可能并非如此。但是,如果您只想 return 消息错误而不是实施补偿和其他内容,这是最简单的方法。有些用例很简单。
关于命令处理程序和业务逻辑,我不同意 Tomasz Jaskuλa 的观点。命令处理程序是一个函数,一个技术细节。您可以将业务逻辑放在命令处理程序或静态函数中,这都没有关系。消息及其处理程序是可用于实现 功能的基础结构组件。例如,在应用程序中,您可以拥有领域事件、应用程序事件等。它们都是事件,即发生变化的通知,您可以拥有驻留在域中或其他地方的事件处理程序。
没有规则阻止您从数据库中 'read',但至少理论上读取模型是陈旧的。然而,在 99% 的情况下,这可能不是这样的问题。对于剩下的 1%,您需要非常具体的解决方案。
我刚刚问了我一个知识渊博的朋友完全相同的问题,他的回答是可以在命令处理程序中进行此验证(在我的例子中,在解释命令并将事件写入的 akka 持久化角色中)日记)。
但是,如果出于性能原因这不可能(因为在持久性 actor 内部处理验证会阻塞 actor,这将成为整个应用程序的可伸缩性瓶颈),则可以使用乐观锁定 (OCC)。
换句话说,验证可以在另一个参与者中执行(我们称之为验证者参与者——它不在持久性参与者中),这不会阻止持久性参与者,但可能会发生数据是用于验证的持久性 actor 已更改,而验证器 actor 中的验证为 运行)。
如果验证器 actor returns 具有 OK 并且所有用于验证的数据在持久性 actor 中仍然相同(具有相同的版本 - 与 OCC 中的版本相同)在命令到达命令处理程序(持久化角色)时,命令被持久化角色接受,否则需要重新提交验证以重新评估给验证者角色。