微服务事件驱动架构的设计选择

时间:2017-11-29 13:39:28

标签: domain-driven-design microservices cqrs

我们假设我们有以下内容:

DDD聚合A和B,A可以引用B。

管理A的微服务公开以下命令:

  • 创建A
  • 删除A
  • 链接A到B
  • 从B
  • 取消链接A.

管理B的微服务公开以下命令:

  • 创建B
  • 删除B

成功创建,删除,链接或取消链接总是会导致执行操作的微服务发出相应的事件。

为这两个微服务设计事件驱动架构的最佳方法是:

  1. A和B总是最终彼此一致。一致性,我的意思是如果B不存在,A不应该引用B.
  2. 两个微服务中的事件可以很容易地投射到一个单独的读取模型中,在该模型中可以跨越A和B进行查询
  3. 具体来说,以下示例可能会导致暂时不一致的状态,但最终必须恢复一致性:

    示例1

    • 初始一致状态:A存在,B不存在,A未链接到B
    • 命令:链接A到B

    示例2

    • 初始一致状态:存在,B存在,A链接到B
    • 命令:删除B

    示例3

    • 初始一致状态:存在,B存在,A未链接到B
    • 两个同时发出的命令:链接A到B并删除B

    我有两个解决方案。

    解决方案1 ​​

    • 微服务A只允许将A链接到B,如果它先前已收到“B已创建”事件且没有“B已删除”事件。
    • 微服务B只允许删除B,如果之前没有收到“A链接到B”事件,或者该事件之后是“A取消链接B”事件。
    • 微服务A侦听“B已删除”事件,并在收到此类事件后,将A与B取消链接(对于在收到B链接到B事件之前删除B的竞争条件)。

    解决方案2:

    • 微服务A始终允许将A链接到B.
    • 微服务B侦听“A链接到B”事件,并且在接收到这样的事件时,验证B存在。如果没有,则会发出“拒绝B链接”事件。
    • 微服务A侦听“B已删除”和“拒绝链接B”事件,并在收到此类事件后,将A与B取消链接。

    编辑:解决方案3,由纪尧姆提出:

    • 如果先前未收到“B已删除”事件,则微服务A仅允许将A链接到B.
    • 微服务B始终允许删除B.
    • 微服务A侦听“B已删除”事件,并在收到此类事件后,将A与B取消链接。

    我在解决方案2中看到的优势是微服务不需要跟踪其他服务发出的过去事件。在解决方案1中,基本上每个微服务都必须维护另一个微服务的读取模型。

    解决方案2的潜在缺点可能是在读取模型中投影这些事件的额外复杂性,尤其是在系统中添加了相同模式的更多微服务和聚合之后。

    对于一种或另一种解决方案是否存在其他(dis)优势,或者甚至是我不知道应该不惜一切代价避免的反模式? 有没有比我提议的更好的解决方案?

    任何建议都将受到赞赏。

2 个答案:

答案 0 :(得分:2)

  

微服务A只允许将A链接到B,如果它先前已收到“B已创建”事件且没有“B已删除”事件。

这里有一个潜在的问题;考虑两条消息link A to BB Created之间的竞赛。如果B Created消息首先到达,则所有内容都按预期链接。如果B Created碰巧到达第二个,则链接不会发生。简而言之,您的业务行为取决于您的消息管道。

Udi Dahan, 2010

  

时间上的微秒差异不应对核心业务行为产生影响。

     

解决方案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 ACommand validator,微服务B包含Aggregate BSaga。重要的是要理解验证器不会阻止系统的无效状态,但只会降低概率。

  

解决方案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都有一个微服务。