具有 CQRS 和 CQRS 的 SO 风格声誉系统事件溯源
我正在深入尝试 CQRS 和事件溯源,有几点需要一些指导。我想实施一个SO风格的声誉系统。这似乎非常适合这种架构。
以SO为例。假设一个问题被投票,这会生成一个 UpvoteCommand
,它会增加问题的总分并触发 QuestionUpvotedEvent
。
看来作者的用户聚合应该订阅 QuestionUpvotedEvent
这可以提高声誉得分。但我不清楚你如何/何时进行此订阅?在 Greg Youngs 示例中,事件/命令处理连接在 global.asax 中,但这似乎不涉及任何基于聚合 ID 的路由。
似乎每个用户聚合都会订阅每个 QuestionUpvotedEvent
这似乎不正确,为了使这样的方案起作用,事件处理程序必须表现出行为来识别该用户是否拥有该问题刚刚投了赞成票。 Greg Young 暗示这不应该出现在事件处理程序代码中,事件处理程序代码应该只涉及状态更改。
我在这里犯了什么错?
非常感谢任何指导。
编辑
我想我们在这里讨论的是问题和问题之间的聚合间通信。用户聚合。我看到的一种解决方案是,QuestionUpvotedEvent
由 ReputationEventHandler
订阅,然后它可以获取相应的 User AR 并调用该对象上相应的方法,例如 YourQuestionWasUpvoted
。这反过来会生成一个特定于用户的 UserQuestionUpvoted
事件,从而保留将来的重播能力。这是朝着正确的方向前进吗?
编辑 2
另请参阅此处有关 Google 群组的讨论一个>。
I am diving into my first forays with CQRS and Event Sourcing and I have a few points Id like some guidance on. I would like to implement a SO style reputation system. This seems a perfect fit for this architecture.
Keeping SO as the example. Say a question is upvoted this generates an UpvoteCommand
which increases the questions total score and fires off a QuestionUpvotedEvent
.
It seems like the author's User aggregate should subscribe to the QuestionUpvotedEvent
which could increase the reputation score. But how/when you do this subscription is not clear to me? In Greg Youngs example the event/command handling is wired up in the global.asax but this doesn't seem to involve any routing based on aggregate Id.
It seems as though every User aggregate would subscribe to every QuestionUpvotedEvent
which doesn't seem correct, to make such a scheme work the event handler would have to exhibit behavior to identify if that user owned the question that was just upvoted. Greg Young implied this should not be in event handler code, which should merely involve state change.
What am i getting wrong here?
Any guidance much appreciated.
EDIT
I guess what we are talking about here is inter-aggregate communication between the Question & User aggregates. One solution I can see is that the QuestionUpvotedEvent
is subscribed to by a ReputationEventHandler
which could then fetch the corresponding User AR and call a corresponding method on this object e.g. YourQuestionWasUpvoted
. This would in turn generated a user specific UserQuestionUpvoted
event thereby preserving replay ability in the future. Is this heading in the right direction?
EDIT 2
See also the discussion on google groups here.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我的理解是聚合本身不应该订阅事件。领域模型仅引发事件。订阅事件的是查询端或其他基础设施组件(例如电子邮件组件)。
域服务旨在处理涉及多个聚合的用例/命令。
在这种情况下我会做什么:
VoteUpQuestionCommand 调用的处理程序:
IQuestionVotingService.VoteUpQuestion(Guid QuestionId, Guid UserId);
这就会影响问题和答案。 user 聚合,在两者上调用适当的方法,例如 user.IncrementReputation(int amount) 和 Question.VoteUp()。这会引发两个事件; 将由查询端处理。
My understanding is that aggregates themselves should not be be subscribing to events. The domain model only raises events. It's the query side or other infrastructure components (such as an emailing component) that subscribe to events.
Domain Services are designed to work with use-cases/commands that involve more than one aggregate.
What I would do in this situation:
The handler for VoteUpQuestionCommand calls:
IQuestionVotingService.VoteUpQuestion(Guid questionId, Guid UserId);
This then fecthes both the question & user aggregates, calling the appropriate methods on both, such as user.IncrementReputation(int amount) and question.VoteUp(). This would raise two events; UsersReputationIncreasedEvent and QuestionUpVotedEvent respectively, which would be handled by the query side.
我的经验法则:如果你进行 AR 间通信,请使用传奇。它将事物保持在事务边界内并使您的链接明确 =>更容易处理/维护。
My rule of thumb: if you do inter-AR communication use a saga. It keeps things within the transactional boundary and makes your links explicit => easier to handle/maintain.
用户聚合应该有一个
QuestionAuthored
事件...该事件订阅QuestionUpvotedEvent
...类似地,它应该有一个QuestionDeletedEvent
并且/或QuestionClosedEvent
,其中它执行正确的处理,例如从QuestionUpvotedEvent
等取消sibscribing。编辑 - 根据评论:
我会实现问题是外部事件源并通过网关处理它。反过来,网关负责正确处理任何重播,因此最终结果保持完全相同 - 除了拒绝事件等特殊事件......
The user aggregate should have a
QuestionAuthored
event... in that event is subscribes to theQuestionUpvotedEvent
... similarly it should have aQuestionDeletedEvent
and/orQuestionClosedEvent
in which it does the proper handling like unsibscribing from theQuestionUpvotedEvent
etc.EDIT - as per comment:
I would implement the Question is an external event source and handle it via a gateway. The gateway in turn is the one responsible for handling any replay correctly so the end result stays exactly the same - except for special events like rejection events...
这是老问题并标记为已回答,但我认为可以添加一些内容。
经过几个月的阅读、实践和基于 CQRS+ES 创建小型框架和应用程序,我认为 CQRS 试图解耦组件依赖关系和职责。在某些为每个命令编写的资源中,您应该在命令处理程序上最多更改一个聚合(您可以在处理程序上加载多个聚合,但只能更改其中之一)。
所以在你的情况下,我认为最好的做法是@Tom回答,你应该使用saga。如果您的框架不支持 saga(例如我的小型框架),您可以创建一些事件处理程序,例如
UpdateUserReputationByQuestionVotedEvent
。其中,处理程序创建UpdateUserReputation(Guid user id, int amount)
或UpdateUserReputation(Guid user id, Guid QuestionId, int amount)
ORUpdateUserReputation(Guid 用户 ID,字符串描述,int 金额)
。命令发送到处理程序后,处理程序通过用户 ID 加载用户并更新状态和属性。在这种类型的处理中,您可以创建更复杂的场景或工作流程。This is the old question and tagged as answered but I think can add something to it.
After few months of reading, practice and create small framework and application base on CQRS+ES, I think CQRS try to decouple components dependencies and responsibilities. In some resources write for each command you Should change maximum one aggregate on command handler (you can load more than one aggregate on handler but only one of them can change).
So in your case I think the best practice is @Tom answer and you should use saga. If your framework doesn't support saga (Like my small framework) you can create some event handler like
UpdateUserReputationByQuestionVotedEvent
. In that, handler createUpdateUserReputation(Guid user id, int amount)
ORUpdateUserReputation(Guid user id, Guid QuestionId, int amount)
ORUpdateUserReputation(Guid user id, string description, int amount)
. After command sends to handler, the handler load user by user id and update states and properties. In this type of handling you can create a more complex scenario or workflow.