33

假设堆栈溢出域问题和以下事件定义:

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样本。但在我看来,它们仅使用事件中包含的数据进行操作。

4

3 回答 3

26

我个人认为从事件处理程序中查找用户名没有任何问题。但是,如果您无法从用户的读取模型中查询名称,那么我会向 QuestionEventsHandler 引入一个额外的事件处理程序,以处理 UserRegistered 事件。

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

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

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

于 2012-02-06T14:29:09.043 回答
4

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

格雷格的方法,我记得, - 在以这种方式创建和存储/发布事件的同时丰富事件。

于 2010-11-03T08:29:55.847 回答
1

从 EventStore 中提取事件。

请记住 - 您的读取模型需要已经对 EventStore 具有只读访问权限。阅读模型是一次性的。它们只是缓存视图。您应该能够随时删除/过期您的读取模型 - 并从 EventStore 自动重建您的读取模型。所以 - 您的 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 类型的事件尤其如此。

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

事件中的附加数据:您真的不想用视图所需的所有额外数据来使事件膨胀。当您的域发生更改并且您需要迁移事件时,它真的会伤害您。领域事件有特定的用途——它们代表状态变化。不查看数据。

希望这可以帮助 -

瑞安

于 2012-01-11T20:12:48.987 回答