CQRS 状态:命令不应该查询读取端。
行。让我们看下面的例子:
用户需要创建带有订单行的订单,每个订单行包含product_id
, price
, quantity
.
它向服务器发送带有订单信息和订单行列表的请求。
服务器( 命令处理程序 )不应该信任客户端,需要验证提供的产品(product_ids)是否存在(否则会有很多垃圾)。
自 命令处理程序 不允许查询读取端,它应该以某种方式在 上验证此信息写边 .
我们在 上有什么写边 : 存储库。就 DDD 而言,存储库仅使用 运行。聚合根 ,存储库只能通过 ID 获取和保存。
在这种情况下,唯一的选择是一一加载所有产品聚合(存储库只有 GET BY ID 方法)。
注意:事件溯源被用作持久性,因此一次加载多个聚合以避免对存储库的多个请求是有问题的并且效率不高)。
这种情况的最佳解决方案是什么?
P.S.:一种解决方案是重新设计 UI(更像是基于任务的 UI),例如:用户首先创建订单(带有一般信息),然后逐个添加产品(每个添加单独的 http 请求),但我仍然需要支持批量操作(以第三方应用程序的 api 为例)。
最佳答案
简短的回答:将域服务(参见 Evans,第 5 章)与其他命令参数一起传递给聚合。
CQRS states: command should not query read side.
这不是绝对的——当您在命令处理程序中包含查询时,需要权衡取舍;这并不意味着你不能这样做。
在 domain-driven-design ,我们有
domain service
的概念,这是一种无状态机制,聚合可以通过它从其自身一致性边界之外的数据中学习信息。因此,您可以定义一个服务来验证产品是否存在,并在添加项目时将该服务作为参数传递给聚合。计算产品是否存在的工作将被抽象到服务接口(interface)后面。
但是您需要记住的是:产品可能是在订单聚合之外定义的。这意味着它们可以与您的检查同时更改以验证 product_id。从正确性的角度来看,在聚合、应用程序的命令处理程序或客户端代码中检查 product_id 的有效性并没有真正的区别。在所有三个地方,您验证的产品状态都可能是陈旧的。
Udi Dahan shared几年前的兴趣观察
A microsecond difference in timing shouldn’t make a difference to core business behaviors.
如果客户端在编写命令时已经验证了一百毫秒前的数据,并且数据是有效的,那么聚合的行为应该是什么?
考虑一个添加产品的命令,该命令与同一产品的订单同时组成 - 从业务角度来看,系统的正确性是否应该取决于这两个命令碰巧到达的顺序?
要记住的另一件事是,通过将此检查引入您的聚合,您将更改聚合的能力与域服务的可用性相结合。如果域服务无法获得所需的数据(因为读取模型已关闭,或其他原因),应该发生什么。它会阻塞吗?抛出异常?做一个猜想?这种选择是否会影响到聚合的设计,等等。
关于validation - CQRS DDD : How to validate products existence before adding them to order?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45007667/