这个问题源于一种不断发展的架构中存在的危机。从本质上讲,如果命令处理程序或事件处理程序更新数据存储,那么当读取模型 命令模型时,会问这个吗?
特别是我很想知道是否有一种可接受的方法可以解决这个问题,如果沿着一条路线走下去可能导致众所周知的问题。
详细说明,我们从域模型和一些存储库开始,通过ORM读取和更新数据存储。这些存储库查询现有的域模型,我们将结果映射到DTO - 我们没有考虑在此时创建单独的读取模型。该体系结构已经发展为使用CQRS方法,因此我们现在发出命令来创建域对象,并在处理命令时引发事件。
此时我们现在有一些相当复杂的模型对象,正在进行各种计算,并且(最终)意识到如果我们坚持这些计算,读取会快得多。到目前为止出乎意料。
有一个想法是通过向当前数据存储表添加字段来持久化这些计算,从而使我们的命令模型更像读取模型。因此有人认为我们应该更新事件处理程序中的数据存储,因为模式表明应该修改读取模型。
即。如果我们发出的CreateItemCommand
由CreateItemCommandHandler
处理并引发ItemCreatedEvent
,而ItemCreatedEventHandler
由某些CreateItemCommandHandler
处理,则ItemCreatedEventHandler
应缩减为仅仅验证命令,数据存储的实际修改应该在ItemCreatedEvent
逻辑看起来很合理,但似乎也会产生违反直觉的架构。我认为CQRS模式要求命令处理程序的成功事件表明域模型中实际发生了某些变化?
我可以理解不想在命令处理程序中更新读取模型但是根本不做任何事情似乎是错误的,特别是因为我们不能相信{{1}}说实话。 / p>
这最终是主观的,还是有其他具体的理由可以采取某种方式?
答案 0 :(得分:4)
我认为你在CQRS论坛上可能会有更好的运气,因为这可能需要更多的来回讨论。加上真正的答案可能是“它取决于”。
那就是说,听起来你正在混合"strict" definition of CQRS更多的“接受”的模式定义。前者仅仅是读逻辑和写逻辑(及其结构)的分离。后者通常被定义为具有高度非规范化的读取模型,并且通常涉及某种形式的事件驱动架构(包括事件源是某些情况)。
听起来你有这两种想法的混合。我的第一个问题是:如果他们没有创建非规范化的读模型,你的事件处理程序会做什么?他们只是通知订阅者使缓存失效并重新获取数据吗?
但是底线,我认为你的直觉是正确的 - 事件是在数据成功更新后发布的。如果一个处理程序负责更新(命令/写入端)数据库,则已经通知其他订户发生了这种情况,如果对数据库的更新失败,则可能不是这样。
因此,您的命令端应该更新数据库,您的读取应该是查询规范化的数据存储并将结果投影到非规范化的结果集。同样,事件(如果使用的话)可能只是用于通知数据已被更新,并且订阅者可能需要重新查询(再次,像缓存失效)。
答案 1 :(得分:3)
“这个问题源于一种不断发展的体系结构中存在的危机。本质上,它归结为要求这一点 - 当读取模型是命令模型时,命令处理程序或事件处理程序是否应更新数据存储? “
模型是否共享并不重要。在一个理想的世界中,你会有分离的模型,但只要你保持事件和命令的处理程序分离你是伟大的,你当然不会受益于数据库优化(读/写),但你已经分裂了问题。不要忘记,现在,你将使用相同的型号,但未来呢?即使您使用相同的模型,通过保持处理程序分离,它将使您更容易更改为分离的模型。无论如何,应该使用存储库模式隐藏该层。命令总是点燃变化的命令,而不是事件;在井结构域中,您可以处理事件并触发另一个命令。
<强>更新强>
为了清楚说明,您可以更改事件处理程序中的更改存储库,但仅限于复制数据,不属于您的服务的数据或读取模型中的数据,即:
假设您有两个服务,客户和仓库,并且在客户服务中,您拥有客户详细信息,例如注册和地址。根据SOA“规则”服务应该是独立的,因此如果放置新订单并且要求仓库分派项目,它必须已经具有地址,因此在结构良好的域中,当客户端被注册时,客户端'service发布一个包含注册数据的事件,其中可能包括地址,仓库服务处理该事件,提取事件的地址部分并将其持久存储在其持久层中。
因此,在此示例中,事件可以在存储库中执行更改,但仅针对镜像数据。我认为重要的是要让它更明确一点,因为我在下面的评论中说,事件不能发生变化。