事件采购和读取模型生成

时间:2010-10-31 16:58:05

标签: domain-driven-design cqrs event-sourcing

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

UserRegistered(UserId, Name, Email)
UserNameChanged(UserId, Name)
QuestionAsked(UserId, QuestionId, Title, Question)

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

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

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

QuestionItem(UserId, QuestionId, QuestionTitle, Question, UserName)

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

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? */);
        ...
    }
}

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

我检查了现有的CQRS样本,包括Greg Young的SimpleSQRS和Mark Nijhof的Fohjin样本。但在我看来,他们只使用事件中包含的数据。

3 个答案:

答案 0 :(得分:23)

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

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

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

对我来说,这似乎比“丰富事件”更省力,并且有利于建立对系统其他部分及其读取模型的依赖。

答案 1 :(得分:3)

只需用所有必要信息丰富事件。

正如我记得的那样,Greg的方法 - 以这种方式创建和存储/发布事件来丰富事件。

答案 2 :(得分:0)

从EventStore中提取事件。

请记住 - 您的阅读模型需要具有对EventStore的只读访问权限。阅读模型是一次性的。它们只是缓存的视图。您应该可以随时删除/过期您的Read Models - 并自动从EventStore重建您的ReadModel。所以 - 您的ReadModelBuilders必须已经能够查询过去的事件。

public class QuestionEventsHandler
{
    public void Handle(QuestionAsked question)
    {
        // Get Name of User
        var nameChangedEvent = eventRepository.GetLastEventByAggregateId<UserNameChanged>(question.UserId);

        var item = new QuestionItem(
            question.UserId, 
            question.QuestionId, 
            question.Title, 
            question.Question, 

            nameChangedEvent.Name
    }
}

同时实现 - EventStore存储库不一定是真正的EventStore,尽管它当然可以。分布式系统的优点是,如果需要,您可以轻松复制EventStore,更接近您的ReadModel。

我遇到了这个完全相同的场景......我需要的数据比单个事件中的数据要多。对于需要使用初始状态填充新的ReadModel的Create-type事件尤其如此。

从阅读模型:您可以从其他阅读模型中提取。但我真的不推荐这个,因为你会引入一个依赖泥的大球,其中视图依赖于视图取决于视图。

事件中的其他数据:您真的不希望使用视图所需的所有额外数据来膨胀您的事件。当您的域名发生变化时,它会对您造成伤害。你需要迁移事件。域事件具有特定目的 - 它们代表状态更改。不查看数据。

希望这有帮助 -

赖安