【发布时间】:2021-05-19 13:01:44
【问题描述】:
在 CQRS 中,当我们需要为我们的读取模型创建定制的投影时,我们通常更喜欢“非规范化”投影(假设我们正在谈论投影到数据库上)。应用程序/UI 所需的信息来自不同的聚合体(可能来自不同的 BC)并不罕见。
假设我们需要一个投影表来包含客户的信息以及她的完整地址,并且Customer 和Address 是我们系统中的不同聚合(可能在不同的 BC 中)。这意味着,地址是独立于客户生成和维护的。或者换句话说,当一个新客户被创建时,并不能保证系统会产生一个AddressCreatedEvent随后,这个事件可能在创建之前已经被处理过了客户。在CreateCustomerCommand 时,我们所拥有的只是现有地址的 UUID。
我们在这里有几种解决方案。
-
丰富
CreateCustomerCommand和随后的CustomerCreatedEvent以包含客户的完整地址(从 UI 或控制器即时查找此信息)。这样,投影处理程序将在收到CustomerCreatedEvent后直接更新表。 -
使用
CustomerCreatedEvent中提供的addrUuid在投影处理程序中执行临时查询,以在更新表之前获取地址信息的缺失部分。
这些是通常讨论的解决此问题的方法。然而,正如许多其他人所指出的,每种方法都存在问题。例如,Enrico Massone in this question 也描述了丰富事件的合理性。查询其他视图/投影(一种 JOIN)会起作用,但会引入耦合(参见相同的链接)。
我想在这里描述另一种方法,我相信它很好地解决了这些问题。如果这是一种已知技术,我事先为没有给予适当的信任而道歉。真诚地,我没有在其他地方看到它的描述(至少没有那么明确)。
正如他们所说:“一张图片说一千个单词”:
这个想法是:
- 我们将
CreateCustomerCommand和CustomerCreatedEvent保持简单,仅使用addrUuid属性(不丰富)。 - 在 API 控制器中,我们向命令处理程序(聚合)发送 两个命令:第一个,像往常一样 -
CreateCustomerCommand以创建客户和项目客户信息以及addrUuid到暂时将其他列(完整地址等)留空的表。 (警告:查看更新,我们可能在此处遇到并发问题,需要从 Saga 发出探测命令。) - 紧接着,在我们获得新创建客户的
custUuid之后,我们向Address发出一个特殊的ProbeAddrressCommand聚合,触发一个AddressProbedEvent,它将封装地址的完整状态以及特殊属性probeInitiatorUuid,当然是我们之前命令中的custUuid。 - 然后,投影处理程序将对
AddressProbedEvent采取行动,只需填写表中缺少的信息片段,通过匹配提供的probeInitiatorUuid(即custUuid)查找所需的行) 和addrUuid。
所以我们有两个阶段:创建Customer 并探测相关的Address。它们在图中分别用 (1) 和 (2) 进行了描述。
显然,我们可以根据投影的需要发送尽可能多的“探测”命令(并行):ProbeBillingCommand、ProbePreferencesCommand 等。有效地填充或“填充”非规范化投影,其中缺少每个数据处理“探测”事件。
这种方法的优点是我们保持第一阶段的命令/事件简单(只有 UUID 到其他聚合),同时避免投影的同步耦合(连接)。整个方法给人一种很好的 EDA 感觉。
那么我的问题是:这是一种已知的技术吗?好像我还没有看到这个......这种方法会出现什么问题?
我会更乐意使用对描述此方法的其他来源的任何引用来更新此问题。
更新 1:
我已经看到这种方法存在一个重大缺陷:命令ProbeAddrressCommand 在投影处理程序有机会处理CustomerCreatedEvent 之前无法发出。但这不可能从 API 网关(或控制器)得知。
解决方案可能会涉及一个 Saga,比如 CustomerAddressJoinProjectionSaga,它将在收到 CustomerCreatedEvent 时开始,然后才会发出 ProbeAddrressCommand。 Saga 将在注册AddressProbedEvent 时结束。或者,如果在探测中涉及到许多其他聚合,则在收到所有此类事件时。
这是更新后的图表。
更新 2:
正如 Levi Ramsey 所指出的(请参阅下面的答案),我的示例在聚合的选择方面相当复杂。事实上,Customer 和Address 通常被概念化为属于一起(相同的聚合根)。因此,考虑类似Student 和Course 这样的问题可以更好地说明问题,为了简单起见,假设两者之间存在直接关系:学生正在学习课程。这样更明显Student和Course是独立的聚合体(学生和课程可以在系统的不同时间和不同地点创建和维护)。
但问题仍然存在:我们如何获得包含有关学生的完整信息(全名等)和她注册的课程(职称、学分、教师全名、先决条件等)的投影。 ) 都在同一个表中,如果 UI 需要的话?
【问题讨论】:
标签: cqrs saga event-driven-design