无服务器事件源和CQRS指南
在本文中,我们将介绍两种相关的架构模式,人们通常会听说过这两种模式,而且通常认为它们是同一件事。它们是事件源(Event Sourcing)和 CQRS(命令查询职责分离)这两种相关模式。
什么是事件源?
“我们可能不想知道当前的状态,而是想知道发生了什么事情才达到现在的状态”
让我们先了解一下什么是事件源/事件溯源:
- 这种软件设计模式涉及将应用程序状态的更改捕获为连续事件日志中的不可变和历史事件。
- 这些事件充当最终参考点,可以重播以在任何给定时刻重建应用程序的当前状态。
- 随后使用投影从事件日志中提取系统的当前状态以供查询,利用事件历史有效地投影当前状态。(即我们使用事件历史来投影当前状态)。
这与非事件源系统(通常是 CRUD)的工作方式非常不同,我们不将事件存储为历史活动的日志,而是不断更新数据库中各个记录的状态。
与基于 CRUD 的解决方案相比,事件源通常解决以下问题:
- 审计:在事件源中,系统状态的每次更改都表示为一个不同的事件,提供全面的操作历史记录(审计),这在 CRUD 系统中通常是所缺乏的。
- 重放和时间旅行:事件源将系统状态的变化作为顺序日志中的不可变事件捕获,与传统的 CRUD 系统不同,可以实现高效的重放和审计能力(这还包括调试问题时的时间旅行)。
- 性能:与 CRUD 系统不同,事件源将写入操作与读取操作分开,通过基于事件日志启用定制的查询机制来促进可扩展性和性能优化。
- 原子:事件源可以帮助防止对同一记录的并发更新,这可能会导致冲突,因为它避免了直接更新数据存储中的对象的要求。
什么是 CQRS?
CQRS划分了处理命令和查询的职责,自然地与事件源保持一致,从而可以明确分离关注点,高效地从事件历史中获取可查询状态。在这方面,我们可以创建最终一致的命令(事件)历史投影,这使我们能够创建可查询的只读视图,以满足消费者的特定需求。
读写工作负载往往是相反的,对性能和规模的要求非常不同。
在传统架构中,单一数据模型处理数据库的查询和更新,这非常适合简单的 CRUD 操作。然而,对于复杂的应用程序,这种方法可能变得繁琐且不切实际;特别是因为写入可能包含复杂的业务逻辑,而查询可能需要专门的数据视图。
最终,读取和写入工作负载通常是相反的,对性能和规模的要求非常不同。
CQRS 通常解决以下问题:
- 优化操作:CQRS 通常解决数据的读写表示之间经常不匹配的问题。
- 性能:CQRS 可以消除使用一个数据存储进行查询和写入数据时出现的性能问题。
为什么要Event Sourcing + CQRS一起使用 ?
为什么事件源和 CQRS 天生就契合?这是因为通过事件源进行的顺序事件日志本身并不能提供出色的查询,但与 CQRS 结合构建一个或多个物化视图以实现高效查询效果很好!
在我们开始具体讨论事件源之前,让我们首先介绍一下下面提到的一些关键术语:
- 聚合— 聚合是相关实体周围的一致性边界。它们是按顺序重播时从单个事件流生成的,在此操作期间,将计算聚合的当前(有效)状态,以便可以用它来处理命令。在我们的示例中,这将是按顺序排列的员工事件,从创建员工开始,然后是后续事件,例如请假和取消休假。
- 命令— 命令是一种意图,它会生成一个新事件,我们会将其添加到事件流并存储在数据库中。例如,REQUEST_LEAVE最终会创建一个名为“ ”的新事件的“ LEAVE_REQUESTED”。在我们的示例中,我们的聚合收到“ ”命令REQUEST_LEAVE,然后我们读取所有事件以生成员工的当前视图,此时我们决定是否接受该命令(即,我们在聚合中应用业务规则或我们称之为不变量)。如果接受,我们将生成“ LEAVE_REQUESTED”事件。
- 不变量— 不变量是我们聚合中的业务规则,它始终需要为真才能确保我们的聚合处于有效状态。例如人事管理系统中,不允许员工在余额为 0 时申请休假。
事件源有哪些优点和缺点?
那么,在我们直接投入之前,事件源有哪些缺点呢?
- 复杂性:事件源可能会给系统带来额外的复杂性,特别是在实施和理解方面。
- 事件版本控制:随着领域的发展,事件可能需要进行版本控制以适应业务需求的变化。管理事件的向后和向前兼容性可能变得复杂,尤其是在具有较长事件历史的系统中。
- 读取性能:根据事件重建实体的当前状态可能需要耗费大量的计算资源,尤其是对于大型事件存储或复杂事件处理而言。
- 数据存储:将每个状态变化存储为事件会导致大量数据,这会增加存储成本和复杂性,尤其是对于高吞吐量的系统。
源码:Github