没有事件采购的CQRS:处理事件日志失败

时间:2017-09-13 14:14:04

标签: events domain-driven-design microservices cqrs distributed-system

由于我没有在CQRS应用程序中使用事件源,我引入了一个简单的事件日志,使我能够更新读取存储。

这意味着对我的应用程序的状态更改包含两个操作:

  • 更新写模型状态,例如SQL INSERT
  • 将事件插入事件日志

两个写操作都必须作为一个原子操作发生。不幸的是,事件日志驻留在另一个数据库中,因此我必须考虑分布式事务。

大多数CQRS样本处理传奇模式,他们似乎都利用事件来源,这使事情变得更加简单。

我的问题是"半完成"国家改变,例如

  • SQL插入成功
  • 事件日志插入失败

我可以提出补偿SQL操作(伪代码):

SQLTransaction.Commit(); // if this fails, all is fine. Nothing to revert
try 
{
    EventLog.Insert(event);
}
catch(Exception ex) 
{
    // Try to undo the SQL stuff.
    CompensatingSQLTransaction().Commit(); 
    // uh-oh! The commit fails!!

    // What now? Do a Retry?
}

是否有任何可以帮助我的概念? 我考虑了以下方案来防止不同步的读取数据库:

  • 每个事件都有一个序列号
  • 如果读取端复制检测到未处理的事件(例如,接收40,然后是42),它将在事件日志中查询事件41。
  • 如果事件41不可用,系统将停止复制任何事件,直到有人仔细查看。

这需要手动维护,但会阻止read-db失去同步。

任何真实的人生经历?

1 个答案:

答案 0 :(得分:4)

  

两个写操作都必须作为一个原子操作发生。

在这一点上提出一个非常重要的问题:为什么?如果远程事件日志与记录簿不同步,那么业务成本是多少?

如果您不需要同步,那么直接的方法是将事件日志的副本放入与写入模型相同的数据库中。 Udi Dahan在Reliable Messaging Without Distributed Transactions中讨论了这种方法。写入事务成功后,您可以将事件从SQL存储复制到远程事件日志。

这为您提供了一个远程事件日志,该日志始终与某些状态保持一致,但不保证会被捕到目前。

这通常足够好;毕竟,事件日志本身是过去的快照,并且记录簿可能正在改变,而事件日志的表示被复制到消费者。

但如果不这样做,您的选择是找到一个提供可接受的折衷的分布式事务引擎,或者如果远程写入失败则使用sagas撤消对本地存储的更改。

严妍对saga pattern in aws的讨论反过来引用了Caitie McCaffrey的2015 talk on sagas in distributed systems,提出了这一点:

  

因为补偿操作也可能失败所以我们需要能够重试它们直到成功,这意味着它们必须是幂等的。

     

在实践中,应该有一个合理的上限。在警告人为干预之前重试。

是的 - 你重试了。