使用事件溯源和CQRS有哪些缺点?

64

事件溯源和CQRS非常好,因为它可以摆脱开发人员陷入一个预先建模的数据库中,除非进行大型数据迁移项目,否则需要在应用程序的生命周期内使用该数据库。 CQRS和ES还有其他好处,如扩展事件存储,审计日志等,这些好处已经在互联网上广泛存在。

但是有什么缺点呢?

以下是我在研究和编写小型演示应用程序后想到的一些缺点:

  1. 复杂: 有些人说ES很复杂。但我想说,拥有一个复杂的应用程序总比拥有一个只能使用查询语言(多个连接、索引等)运行非常受限制的查询的复杂数据库模型要好。我的意思是,一些编程语言,如Scala,具有非常丰富的集合库,非常灵活,可以产生一些非常复杂的聚合,而且还有Apache Spark,它使查询分布式集合变得容易。但是,数据库将始终受到其查询语言功能的限制,并且分发数据库比分发应用程序代码更困难 (只需在另一台机器上部署另一个实例!)
  2. 高磁盘空间使用率:事件存储可能会使用大量磁盘空间存储事件。但我们可以每隔几周安排一次清理,并创建快照,也许我们可以在外部硬盘上本地存储历史事件,以防将来需要旧事件?
  3. 高内存使用率:每个域对象的状态都存储在内存中,这可能会增加RAM的使用量,而我们都知道RAM有多么昂贵。 大问题!因为我很穷!有什么解决方案吗?也许使用Sqlite而不是在内存中存储状态?通过在应用程序中引入多个Sqlite实例,是否使事情更加复杂?
  4. 启动时间较长:在故障或软件升级时,启动时间取决于事件数量。但我们可以使用快照来解决这个问题?
  5. 最终一致性:某些应用程序的问题。想象一下,如果Facebook用Event sourcing和CQRS来存储帖子,考虑到Facebook的系统有多忙,如果我发布了一个帖子,我可能要等到第二天才能看到我的fb帖子 :)
  6. 事件存储中的序列化事件:事件存储将事件作为序列化对象存储,这意味着我们无法查询事件存储中事件的内容,而且这也是不鼓励的。而且,我们将来也无法向事件中添加另一个属性。解决方案是将事件存储为JSON对象而不是序列化事件?但这是一个好主意吗?或者增加更多的事件以支持对原始事件对象的更改?

请有人评论我提出的缺点,并纠正我是否错误并建议我可能遗漏的任何其他缺点?


7
为什么每个领域对象的状态都要存储在内存中?当需要使用领域对象时,它们会从事件中重新创建。为什么你认为 Facebook 不会使用事件溯源,仅仅是因为他们很忙?我的理解是他们使用一个写主节点和多个只负责读取的从节点完成“最终一致性”同步,所以他们确实使用了最终一致性,这也是为什么有时候你发了一条帖子却无法在自己的动态里看到它的原因。 - Sam Holder
2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Sam Holder
3
事件溯源并不意味着最终一致性。您可以使用事件溯源并立即达到一致性。 - Phil Sandler
3
您可以立即开始使用CQRS并保持一致。 - Phil Sandler
2
@cmr 看起来你混淆了许多不同的概念。CQRS、事件溯源和最终一致性都是独立的概念,可以各自实现。 - Sam Holder
显示剩余4条评论
5个回答

54

下面是我的看法。

  1. CQRS + ES可以在复杂的软件系统中通过拥有丰富的领域对象、简单的数据模型、历史记录跟踪、更多并发问题的可见性、可扩展性等方面使事情变得更简单。但这确实需要一种不同的思考方式来考虑系统,因此可能很难找到合格的开发人员。但CQRS使得在开发人员之间更容易分离责任。例如,初级开发人员可以纯粹地使用读取端而无需触及业务逻辑。

  2. 数据副本肯定会需要更多的磁盘空间。但现在存储相对便宜。这可能需要IT支持团队做更多的备份和规划如何在出现问题时恢复系统。然而,服务器虚拟化现在使工作流程更加简化。此外,没有单片式数据库可以更轻松地创建系统冗余。

  3. 我不认为更高的内存使用量是一个问题。业务对象应该按需进行加载。对象不应该保留已经被持久化的事件的引用。并且仅在持久化数据时才应发生事件加载。在读取端,您不需要实体 - > DTO - >视图模型转换,这通常发生在分层系统中,并且您不会有任何完整特性ORM通常进行的对象更改跟踪。大多数系统执行的是显著的读取操作而不是写入操作。

  4. 如果使用多个异构数据库,长时间的启动时间可能会成为一个轻微的问题,因为需要初始化各种数据上下文。但是,如果您使用像ADO .NET这样简单的工具来与事件存储进行交互,并使用读取端的微型ORM,那么该系统的“冷启动”速度将比任何全功能ORM都要快。这里重要的是不要过于复杂地访问数据。这实际上是CQRS应该解决的问题。正如我之前所说的,读取端应该针对视图进行建模,而不应具有重新映射数据的任何开销。

  5. 在我看来,两阶段提交可以很好地适用于不需要为数千用户扩展的系统。您需要选择与分布式事务协调器兼容的数据库。例如,PostgreSQL可以很好地适用于读写分离模型。如果系统需要针对大量并发用户进行扩展,则必须考虑最终一致性设计。有些情况下,您可能会有聚合根或上下文边界,这些不使用CQRS来避免最终一致性。这对于域中的非协作部分是有意义的。

  6. 如果您选择了适合事件存储的正确数据库,可以以序列化格式(如JSON或XML)查询事件,但仅限于分析目的。除了按聚合根ID和事件类型查询事件之外,系统内部不应查询事件存储中的任何内容。该数据将被索引,并存储在序列化事件之外。


感谢您提供如此详细的答案,这让我对事情有了更清晰的认识。 - user794783
1
我只想补充一下第5点。我们应该避免使用两阶段提交。最好的解决方案是订阅事件存储并在可能的情况下进行处理,但不要将事件存储与分布式事务绑定。 - Narvalex

14
仅就第5点发表评论。我被告知Facebook确实使用ES(事件源)和最终一致性,这就是为什么您有时会在发布帖子后看到帖子消失然后重新出现的原因。
通常,您的浏览器访问的读模型位于“靠近”您的位置,但是在您发布帖子之后,SPA切换到一个接近您的写模型的读模型。写模型(事件)和读模型之间的接近程度意味着您可以看到自己的帖子。
然而,15分钟后,您的SPA将切换回更接近的第一个读模型。如果包含您的帖子的事件尚未传播到该读模型,则您会看到自己的帖子消失,只有在稍后才会重新出现。

非常有趣! - Andrea Ligios

10

我知道这个问题被提出已经快3年了,但是对于一些人来说可能这篇文章仍然有用。其关键点包括:

  • 使用快照进行扩展
  • 数据的可见性
  • 模式更改
  • 处理复杂领域
  • 需要向大多数新团队成员解释它

7
事件溯源和CQRS很好,因为它可以避免开发人员被困在一个预建模的数据库中,在应用程序的生命周期内必须使用该数据库,除非进行大数据迁移项目。
这是一个大误解。关系数据库正是为了模型的演变而发明的(多亏了简单的二维表格,而不是预定义的分层结构)。通过视图和过程确保数据访问的封装,逻辑模型和物理模型可以独立演化。这也是SQL将DDL和DML定义为相同语言的原因。一些RDBMS还允许所有这些演化版本化并在线部署(持续交付),例如Oracle Edition Based Redefinition。
大数据结构是预定义的,并且只能使用为该结构开发的代码进行读取。当立即使用时,这是可以接受的,但是10年后,如果没有确切的版本和语言编译器或解释器,您将很难阅读它。

5

我希望不晚尝试给出一个答案。在这些月里,我对这个问题进行了很多研究,目的是为我的架构的某些部分实现一个生产级别的解决方案,在那些地方ES可能有意义。

复杂性: 其实,它不应该是复杂的,它的任务是致命简单的。怎么做呢?把所有的复杂性从业务逻辑代码转移到基础设施代码上。数据访问应该由尚未成熟的框架完成。但是,ES/CQRS之争中还没有明显的赢家,也许是因为这仍然是一种小众/潮流方法(?)。所以有些团队正在推出自己的解决方案或采用一些现成的技术,比如Axon。

高磁盘空间使用率: 我想说更多,我想说*潜在无限*的磁盘使用率。但是如果你选择ES,那么你也有一个非常好的理由来容忍这个表面上的缺点。让我们列举一些:

  1. 审计日志 : 数据存储是一个事件日志,我们已经知道了。金融应用程序或每个使命/安全性关键的应用可能需要一个中央审计日志,它能够记录什么时候做了什么事情。ES提供了这个功能...你还可以使用一些业务相关的元数据装饰你的事件条目(例如与某个API消费者标识相关的交易ID、操作的严重程度等)。

  2. 高并发: 有一些系统,其中逻辑资源状态会被多个客户端以并发方式改变。这些是游戏、IoT平台等。记录事件而不是更改状态表示可以是提供事件的完全顺序的聪明方式。另一种方法是将同步工作委托给数据库。但如果你想使用ES,这不是你想要的。

  3. 分析:假设你有大量的具有很高商业价值的数据,但你仍然不知道哪个最有价值。多年来,我们通过使用不同信息模型翻译数据组织从应用程序信息中提取知识(OLAP cube)。事件存储器再次提供了类似的功能。事件日志是信息表示的最原始形式,你可以有许多处理它们的方式,批处理或针对存储的事件进行反应。

高内存使用率: 一旦构建了投影,我认为它应该是相同的。

较长的启动时间: 如果读取端缓存了其投影并“记住”最后一次更新事件,则不应重新应用整个事件序列。快照是缓解方法,但如果你做很多快照,那么可能选择ES是错误的。我认为在微服务生态系统中,这个问题是比较小的,因为启动时间可以掩盖没有服务中断。事实上,在应用ES/CQRS时,你会得到最好的结果。

事件ual consistency(最终一致性): 这与ES并无关系,应该归咎于CAP定理。许多非ES/CQRS都需要处理这个问题,但存在很多情况下,这不是实际问题。在ES适用的场景中,您可以将ES和非ES服务混合使用于同一平台。

事件存储中的序列化事件: 如果需要具有非序列化事件表示形式,则可以使用面向文档的数据库。但是,如果您这样做是为了查询事件负载,则没有使用ES/CQRS的意义。ES的含义是将所有数据操作从数据库侧移到应用程序层,在此层中,每个部分都会快速更改,且全部状态无关。这提高了可扩展性和容错性,并提供了一种方式来塑造团队组织,例如让前端开发者轻松地使用JavaScript编写他/她的BFF。

我希望能够将这些原则付诸实践,并获得良好的结果,从而享受到这种激动人心的方法所带来的好处。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接