假设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样本。但在我看来,他们只使用事件中包含的数据。
答案 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事件尤其如此。
从阅读模型:您可以从其他阅读模型中提取。但我真的不推荐这个,因为你会引入一个依赖泥的大球,其中视图依赖于视图取决于视图。
事件中的其他数据:您真的不希望使用视图所需的所有额外数据来膨胀您的事件。当您的域名发生变化时,它会对您造成伤害。你需要迁移事件。域事件具有特定目的 - 它们代表状态更改。不查看数据。
希望这有帮助 -
赖安