我正在深入探索CQRS和事件采购的第一次尝试,我有一些观点,我喜欢一些指导。我想实现一个SO风格的声誉系统。这似乎非常适合这种架构。
以SO为例。如果对某个问题进行投票,则生成UpvoteCommand
会增加问题总分并触发QuestionUpvotedEvent
。
似乎作者的用户聚合应订阅QuestionUpvotedEvent
,这可能会提高声誉得分。但是,如何/何时进行此订阅对我来说并不清楚?在Greg Youngs示例中,事件/命令处理在global.asax中连接,但这似乎不涉及基于聚合ID的任何路由。
似乎每个User聚合都会订阅每个似乎都不正确的QuestionUpvotedEvent
,为了使这样的方案工作,事件处理程序必须展示行为以识别该用户是否拥有该问题。刚刚投了赞成票。 Greg Young暗示这不应该在事件处理程序代码中,这应该只涉及状态更改。
我在这里弄错了什么?
非常感谢任何指导。
修改
我想我们在这里谈论的是问题与解决方案之间的聚合间沟通。用户聚合。我可以看到的一个解决方案是QuestionUpvotedEvent
由ReputationEventHandler
订阅,然后YourQuestionWasUpvoted
可以获取相应的用户AR并在该对象上调用相应的方法,例如UserQuestionUpvoted
。这将反过来生成用户特定的{{1}}事件,从而在将来保留重放能力。这个方向是朝着正确的方向发展吗?
编辑2
另请参阅有关Google群组here的讨论。
答案 0 :(得分:6)
我的理解是聚合本身不应该订阅事件。域模型仅引发事件。它是订阅事件的查询方或其他基础结构组件(例如电子邮件组件)。
域服务旨在处理涉及多个聚合的用例/命令。
在这种情况下我会做什么:
VoteUpQuestionCommand 的处理程序调用:
IQuestionVotingService.VoteUpQuestion(Guid questionId,Guid UserId);
然后将这个问题和& user聚合,在两者上调用适当的方法,例如user.IncrementReputation(int amount)和question.VoteUp()。这会引发两件事; UsersReputationIncreasedEvent 和 QuestionUpVotedEvent ,分别由查询方处理。
答案 1 :(得分:2)
我的经验法则:如果您进行AR间通信,请使用传奇。它将事物保持在事务边界内并使您的链接显式=>更容易处理/维护。
答案 2 :(得分:0)
用户聚合应该有QuestionAuthored
个事件......因为该事件订阅了QuestionUpvotedEvent
...类似地,它应该有QuestionDeletedEvent
和/或{{1}在其中进行正确处理,如从QuestionClosedEvent
等取消订阅
编辑 - 根据评论:
我会实现Question是一个外部事件源并通过网关处理它。反过来,网关负责正确处理任何重播,因此最终结果保持完全相同 - 除了拒绝事件等特殊事件......
答案 3 :(得分:0)
这是一个老问题并标记为已回答,但我认为可以添加一些内容。
经过几个月的阅读,实践并创建了基于CQRS + ES的小框架和应用程序,我认为CQRS试图解除组件的依赖关系和责任。在某些资源中,为每个命令写入应更改命令处理程序上的最大一个聚合(您可以在处理程序上加载多个聚合,但只有一个可以更改)。
所以在你的情况下,我认为最好的做法是@Tom回答,你应该使用传奇。如果你的框架不支持saga(比如我的小框架),你可以创建一些事件处理程序,如UpdateUserReputationByQuestionVotedEvent
。在那里,处理程序创建UpdateUserReputation(Guid user id, int amount)
OR UpdateUserReputation(Guid user id, Guid QuestionId, int amount)
OR
UpdateUserReputation(Guid user id, string description, int amount)
。在命令发送到处理程序之后,处理程序按用户标识加载用户并更新状态和属性。在这种类型的处理中,您可以创建更复杂的场景或工作流程。