我正在编写我的第一个CQRS应用程序,假设我的系统调度了以下命令:
目前,这些导致相同的事件,只是过去时(措辞,ContingentCreated,TeamCreated等),但它们包含相同的属性。 (我不确定这是否正确,这是我的一个问题)
我的问题在于阅读模型。
我有一个Contingents读取模型,订阅了ContingentCreated,并且有以下方法:
List<ContingentQueries.Contingent> GetContingents();
ContingentQueries.Contingent GetContingent(System.Guid id);
ContingentQueries.Contingent GetContingent(string province);
这些返回的Contingent类看起来像这样:
public class Contingent
{
public Guid Id { get; internal set; }
public string Province { get; internal set; }
}
这适用于列表,但是当我开始考虑单个特遣队及其所有团队的视图的读取模型时,事情开始变得混乱。鉴于这些方法似乎微不足道:
ContingentQueries.Contingent GetContingent(System.Guid id);
ContingentQueries.Contingent GetContingent(string province);
我使用一组团队为这些查询创建了Contingent类:
public class Contingent
{
public Guid Id { get; internal set; }
public string Province { get; internal set; }
public IList<Team> Teams { get; internal set; }
}
public class Team
{
public Guid Id { get; internal set; }
public string Name { get; internal set; }
}
理论上,我只需要订阅ContingentCreated和TeamAssignedToContingent,但TeamAssignedToContingent事件只有团队和偶然ID ...因此我无法设置此读取模型的Team.Name属性。
我是否也订阅了TeamCreated?添加另一个团队副本供在这里使用吗?
或者当事件被提出时,他们是否应该有更多信息? AddTeamToContingent命令处理程序是否应该查询要添加到事件的团队信息?这可能还不存在,因为读取模型可能尚未与团队一起更新。
如果我想显示团队名称和已分配给团队的参与者,并在此特遣队视图中命名队长,该怎么办?我是否也将参与者存储在阅读模型中?这似乎有点重复。
对于一些额外的背景。参与者不一定是任何特遣队或团队的一部分,他们可能只是客人;或特遣队的代表和/或备件。但是,角色可以改变。非团队成员的代表也可能是一名备用人员,由于受伤将被分配给团队,但他们仍然是代表。这就是使用通用“参与者”的原因。
我理解我希望阅读模型不会比必要的重,但我很难理解我可能需要来自我没有订阅的事件(TeamCreated)的数据,理论上不应该,但由于未来的事件(TeamAssignedToContingent),我确实需要这些信息,我该怎么办?
更新:一夜之间想到这一点,似乎我想到的所有选项都很糟糕。必须有另一个。
选项1 :向已提升的事件添加更多数据
选项2 :让事件处理程序订阅更多活动吗?
选项3 :让事件处理程序查询其他读取模型吗?
选项4 :...?
答案 0 :(得分:5)
您可能最终会得到的是1和2的组合。增强或丰富事件以获取更多信息绝对没有问题。这实际上非常有用。
您可以将其添加到未来的活动中。如果您需要历史数据,您必须在其他地方找到它。您不能在世界上的所有数据上构建事件,只是因为您将来需要它。甚至允许返回并向您的历史事件添加更多数据。有些人会因为重写历史而对此不屑一顾,但你不是,你只是在改善历史。只要您不改变现有数据,您应该没问题。
我怀疑你的事件处理程序也需要随着时间的推移订阅更多事件。例如,团队是否需要重命名?如果是这样,那么您将需要处理该事件。不要害怕在不同的阅读视图中拥有相同数据的多个副本。来自关系数据库背景,这种重复数据是最难以使用的事情之一。
最终务实。如果您在应用CQRS时遇到痛苦,请更改您应用它的方式。
答案 1 :(得分:1)
我想到的另一个选择是:
在您的阅读方面,您首先要通过写入方面的事件构建规范化视图。
E.g:
contingents table:
-----------
id, name
teams table:
-----------
id, name
contigents_teams table:
-----------
contigent_id, team_id
在 ContingentCreated 事件中,您只需将记录插入特遣队表。
在 TeamCreated 事件中,您只需将记录插入团队表。
在 TeamAssignToContingent 事件中,您将记录插入 contigents_teams 表。
在 TeamNameChanged 事件中,您可以更新球队表中的相应记录。
等等。
因此,您的数据(最终)一致并与写入方同步。
是的,你需要在读取端使用连接来获取数据......
如果此规范化视图不满足读取性能需求,则可以从此规范化数据构建非规范化视图。 它需要一个额外的步骤来构建非规范化视图,但对我来说,这是我现在能想到的最简单的解决方案。
也许你甚至不需要非规范化的观点。
毕竟(在我看来),ES的主要价值在于捕获用户意图,对数据的信心(没有破坏性操作,单一事实来源),能够回答过去没人能想到的问题,大分析的价值。
正如我所说,如果你需要优化性能,你总是可以从读取端的规范化视图构建非规范化视图。