我不经常在StackOverflow上写东西,但我想分享一下我对CQRS / ES实现的理解。我对很多不同组件的责任感到困惑,我想我终于开始讨论这个话题了。如果你能告诉我我的理解是否正确,那就太好了。
一步一步:
用户创建并触发Command,该Command是表示在域中执行请求的更改所需的最小值的对象。该命令已分配处理函数。在具有CommandBus的方案中,分离的Dispatcher将触发CommandHandler以执行操作。 CommandBus可以使用任何消息传递系统,数据库实现,也可以仅在内存中使用。
CommandHandler的职责是验证命令,然后根据业务规则创建一个或多个事件并将其发送到EventStore。
可以在Aggregate上调用命令。 Aggregate是一个由一个或多个实体和ValueObject组成的对象。 Aggregate的目的是不仅可以自己验证命令,还可以在“聚合”状态的上下文中验证命令。
可以使用邮件系统或数据库实现EventStore。事件总是以过去分词动词命名,例如OrderConfirmed。它们由EventHandlers使用。
EventHandler是一个负责“投影”的功能,这意味着实际存储数据库中的内容。它还可以执行其他操作,例如发送电子邮件,一切都取决于事件的初始目的。
如果我在任何方面都错了,请纠正我。我还有几个问题。
在CommandHandler生成多个事件以保存数据库中的多个对象的情况下,我可以为它们分配相同的UUID,以后我也会保存在所有投影对象中吗?
EventHandler可以触发其他命令还是创建其他事件?在这种情况下,最佳做法是什么?
感谢您的回答。
答案 0 :(得分:3)
命令是包含描述需要发生的操作的所有必要信息的消息。
关联的命令处理程序在聚合根上调用该命令。聚合根验证该命令,然后才调度一个或多个事件。事件处理程序负责将数据投影到读取存储中。可以使用Sagas或状态处理器订阅事件并发布其他消息。
验证需要在聚合根目录中进行。命令处理程序是另一个可以进行简单数据验证的地方。但是,事件处理程序不是数据验证的地方。如果在事件处理程序中放置验证,命令将成功执行,但最终会在读取存储和订阅事件的任何系统中最终出现不一致的数据。
事件确实表示过去由有效命令/聚合根分派的内容。每个事件都必须被视为有效,因为重新加载数据后必须由相应的聚合根重放。业务逻辑可能会在聚合根中随时间发生变化,但不应影响事件处理程序。可以使用多个版本的事件和相同类型的相应处理程序来实现此目的。
所有事件,包括有关聚合根(类型,UUID等)的信息都应存储在事件存储中。事件存储必须一致且持久,因为事件是数据源。
我希望这些信息能够让您了解事件处理的工作原理。我现在试着回答这些问题。
所有这些事件(通常)应该具有相同的聚合根ID和类型(命名空间+类),因此它们可以由相应的聚合根实例重放。它们还应具有确定其发生顺序的版本号。
我在第二段中介绍了这一点。
答案 1 :(得分:1)
一个命令,它是一个对象,表示在域中执行请求的更改所需的最小值
不完全正确。它是一条消息,或者至少是消息的表示,而不是“对象”。 Applications aren't object oriented at the boundaries,也不是域名模型。
EventHandler的职责是验证命令,然后根据业务规则创建一个或多个事件并将其发送到EventStore。
这有点困惑 - 你可能意味着那里的命令处理程序
可以在Aggregate上调用命令。
非常接近 - 在 Aggregate Root 上调用该命令更为正确。在最初的Evans公式中,聚合将指定其边界内的一个实体作为聚合根,也就是说它将是所有命令和查询的一个访问点。
在CQRS文献中,命令和查询是分开处理的;聚合及其根目录仅用于命令。
可以使用消息传递系统或数据库来实现EventStore。
EventStore扮演着记录册的角色;这意味着它需要支持最小的持久性保证 - 除非您“确定”该事件可用于在重新启动时重新加载聚合,否则不应允许任何人查看事件。
事件总是以过去分词动词命名,例如OrderConfirmed。它们由EventHandlers使用。
事件,如命令,只是消息;它们可以任何你喜欢的方式消费。
EventHandler是一个负责“投影”的功能,这意味着实际存储数据库中的内容。
“投影”通常被理解为创建某些事件历史(由一个或多个来源生成)的非权威表示。动机是在从历史中创建表示方面通常需要做大量的工作;如果您正在尝试支持低延迟查询 - 并且对于进行更改和更改可见之间的某个时间间隔感到满意,则可以通过创建更适合快速查询的数据结构来改善延迟(或者, ,只需预先加载一个缓存,其中包含查询答案的表示。)
注意:非权威很重要。这个数据库不是记录簿,事件存储是。
在CommandHandler生成多个事件以保存数据库中的多个对象的情况下,我可以为它们分配相同的UUID,以后我也将保存在所有投影对象中吗?
这个问题不明确。投影对象将根据事件构建,因此您通常希望投影对象中的任何UUID都是您从事件中的数据派生的东西。
此外,它通常不是生成事件的命令处理程序 - 这是域模型的责任;这意味着它发生在聚合根之后。
EventHandler可以触发其他命令还是创建其他事件?在这种情况下,最佳做法是什么?
一个常见的模式是进程管理器,它是一个状态机:您将事件传递给它以从一个状态转换到另一个状态,然后查询它以找出不应该分派哪些命令。