事件采购和读取模型生成

Dmi*_*ich 28 domain-driven-design cqrs event-sourcing

假设Stack Overflow域问题和以下事件定义:

UserRegistered(UserId, Name, Email)
UserNameChanged(UserId, Name)
QuestionAsked(UserId, QuestionId, Title, Question)
Run Code Online (Sandbox Code Playgroud)

假设事件存储的状态如下(按出现顺序):

1) UserRegistered(1, "John", "john@gmail.com")
2) UserNameChanged(1, "SuperJohn")
3) UserNameChanged(1, "John007")
4) QuestionAsked(1, 1, "Help!", "Please!")
Run Code Online (Sandbox Code Playgroud)

假设以下非规范化读取模型列出问题列表(对于SO的第一页):

QuestionItem(UserId, QuestionId, QuestionTitle, Question, UserName)
Run Code Online (Sandbox Code Playgroud)

以下事件处理程序(构建非规范化读取模型):

public class QuestionEventsHandler
{
    public void Handle(QuestionAsked question)
    {
        var item = new QuestionItem(
            question.UserId, 
            question.QuestionId, 
            question.Title, 
            question.Question, 
            ??? /* how should i get name of the user? */);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是如何找到提出问题的用户的姓名?或者更常见的是:如果我的非规范化读取模型需要在特定事件中不存在的附加数据,我应该如何处理事件?

我已经研究CQRS包括现有的样本SimpleSQRS格雷格年轻的Fohjin马克Nijhof的样品.但在我看来,他们只使用事件中包含的数据.

Chr*_*ray 24

我个人认为从事件处理程序中查找用户名是没有错的.但是如果你处于无法从User的读取模型中查询名称的位置,那么我将向QuestionEventsHandler引入一个额外的事件处理程序来处理UserRegistered事件.

这样,QuestionEventsHandler可以维护自己的用户名存储库(您不需要存储用户的电子邮件).然后,QuestionAsked处理程序可以直接从它自己的存储库中查询用户的名字(正如Rinat Abdullin所说,存储很便宜!).

此外,由于您的QuestionItem读取模型包含用户的名称,因此您还需要处理QuestionEventsHandler中的UserNameChanged事件,以确保QuestionItem中的名称字段是最新的.

对我而言,这似乎比"丰富事件"更省力,并且有利于依赖于系统的其他部分及其读取模型.


Rin*_*lin 4

只需使用所有必要的信息来丰富活动即可。

我记得格雷格的方法是——在以这种方式创建和存储/发布活动的同时丰富活动。

  • 这个答案表明所需的所有数据都应该在事件中。作为经验法则,我不同意这一点。事件处理程序将创建非规范化记录,通常包含不一定来自单个聚合的聚合和计算字段。也许以“NumberOfQuestionsByMonth”视图模型为例。这就是CQRS的重点;它在持久化某些内容时而不是在查询时执行这些计算。要进行这些计算,您通常需要查询和处理数据。此数据超出聚合范围,无法在事件中传递。 (11认同)
  • 这似乎是一个混乱的解决方案。如果您想连接两个聚合,则可以监听两个聚合的事件,或者投影到一个可以为您执行连接的读取模型中。 (4认同)
  • 我也不同意,因为一个缺点可能是您需要更改过去发生的所有事件以包含附加数据(假设您从一开始就不知道稍后可能需要哪些数据)时间点)。您肯定不想包含将来可能感兴趣的所有内容。 (3认同)