我们假设我们有以下内容:
DDD聚合A和B,A可以引用B。
管理A的微服务公开以下命令:
管理B的微服务公开以下命令:
成功创建,删除,链接或取消链接总是会导致执行操作的微服务发出相应的事件。
为这两个微服务设计事件驱动架构的最佳方法是:
具体来说,以下示例可能会导致暂时不一致的状态,但最终必须恢复一致性:
示例1
示例2
示例3
我有两个解决方案。
解决方案1
解决方案2:
编辑:解决方案3,由纪尧姆提出:
我在解决方案2中看到的优势是微服务不需要跟踪其他服务发出的过去事件。在解决方案1中,基本上每个微服务都必须维护另一个微服务的读取模型。
解决方案2的潜在缺点可能是在读取模型中投影这些事件的额外复杂性,尤其是在系统中添加了相同模式的更多微服务和聚合之后。
对于一种或另一种解决方案是否存在其他(dis)优势,或者甚至是我不知道应该不惜一切代价避免的反模式? 有没有比我提议的更好的解决方案?
任何建议都将受到赞赏。
答案 0 :(得分:2)
微服务A只允许将A链接到B,如果它先前已收到“B已创建”事件且没有“B已删除”事件。
这里有一个潜在的问题;考虑两条消息link A to B
和B Created
之间的竞赛。如果B Created
消息首先到达,则所有内容都按预期链接。如果B Created
碰巧到达第二个,则链接不会发生。简而言之,您的业务行为取决于您的消息管道。
时间上的微秒差异不应对核心业务行为产生影响。
解决方案2的潜在缺点可能是在读取模型中投影这些事件的额外复杂性,尤其是在系统中添加了相同模式的更多微服务和聚合之后。
我根本不喜欢那种复杂性;对于不太重要的商业价值来说,这听起来很多。
异常报告可能是一种可行的替代方案。 Greg Young talked about this in 2016。简而言之;拥有一台可以检测不一致状态的监视器,以及这些状态的修复可能就足够了。
稍后添加自动补救措施。 Rinat Abdullin described这一进展非常顺利。
自动化版本最终看起来像解决方案2;但是责任分离 - 补救逻辑不在微服务A和B之外。
答案 1 :(得分:2)
您的解决方案似乎没问题,但有些事情需要澄清:
在DDD中,聚合是一致性边界。无论收到什么命令以及该命令是否成功,Aggregate始终处于一致状态。但这并不意味着从业务角度来看整个系统处于允许的永久状态。有时系统整体处于不允许的状态。只要最终它将在允许状态下转换,这是可以的。这是 Saga/Process managers 。这正是他们的作用:使系统处于有效状态。它们可以作为单独的微服务部署。
我在CQRS项目中使用的另一种组件/模式是最终一致的命令验证器。它们在使用私有读取模型到达聚合之前验证命令(如果它不是有效,则拒绝它)。这些组件可以最大限度地减少系统进入无效状态时的情况,并且可以补充Sagas。它们应该部署在包含Aggregate的微服务中,作为域层(聚合)顶部的层。
现在,回到地球。您的解决方案是Aggregates,Sagas和最终一致的命令验证的组合。
解决方案1
- 微服务A只允许链接A到B,如果它先前已经接收到" B创建的"事件,没有" B删除"事件
- 微服务A收听" B删除"事件,并在收到此类事件后,取消A与B的联系。
在此架构中,微服务A包含Aggregate A
和Command validator
,微服务B包含Aggregate B
和Saga
。重要的是要理解验证器不会阻止系统的无效状态,但只会降低概率。
解决方案2:
- 微服务A始终允许将A链接到B.
- 微服务B侦听" A链接到B"事件,并在收到此类事件后,验证B是否存在。如果不是,那就是 发出拒绝B"的链接"事件
- 微服务A侦听" B删除"并且"链接到B拒绝"事件,并在收到此类事件后,取消A与B的联系。
在这个架构中,微服务A包含Aggregate A
和一个传奇,Microservice B
包含Aggregate B
以及一个传奇。如果B上的Saga将验证B的存在并将Unlink B from A
命令发送给A而不是产生事件,则可以简化此解决方案。
在任何情况下,为了应用SRP,您可以将Sagas提取到他们自己的微服务中。在这种情况下,每个Aggregate和每个Saga都有一个微服务。