你有没有想过实现事件源?它在实际应用中有效吗?它如何与领域驱动设计或事件驱动架构等其他模式和方法相结合?如果你对这些问题感兴趣,请继续阅读;在我深入研究这一模式(包括代码!天哪!)之后,我将分享我的看法。
一个实际的例子:银行账户作为一个务实的人来说,我发现用实际例子更容易理解理论概念。在这篇文章里,我将通过一个虚构的金融公司里的银行账户子域来解释我对事件源的探索。
账户管理:
- 账户的创建、关闭及其他可能的状态。
- 记录并验证账户中的存款和取款。
- 在任何时候保持账户余额的准确。
银行的相关背景信息和数据如下:
- 我们正在处理数百万个账户。
- 每天有数百万笔的资金流动。
- 该系统将作为一个平台,在这家假想的金融公司里为其他系统服务。
- 该公司使用Apache Pulsar在多个界线上下文中异步通信。
有了这些基础条件,让我们看看事件源如何帮助满足可扩展系统的需求。
银行账户视为事件流程。事件溯源(Event Sourcing)确保应用程序状态的所有变更都被存储为事件序列。Fowler, Martin. “Event Sourcing”。
为什么要用银行账户聚合功能来探索事件溯源?
- 交易的本质: 银行账户等金融实体非常适合采用事件溯源,因为每一笔交易本质上都是一种异步事件(例如借方或贷方)。
- 审计与追踪: 事件溯源提供内建的审计轨迹,使报告和调查变得简单。
- 状态重建: 可以轻松重建任何给定时刻的状态(比如计算某一特定时间点的余额)。
- 与事件驱动架构的契合度: 每个事件(交易)都可以轻松触发下游流程或更新其他业务上下文,这与EDA相吻合。
银行账户的操作可以记录为事件,这些事件将用于回放和重建账户的情况。常见的事件包括:
- 顺利流程事件:账户发起,账户开立,账户充值成功,账户扣款成功 或 账户关闭
- 失败事件,例如 账户开立失败 或 账户充值失败
- 以及其他可以稍后添加的事件,例如 账户冻结,借记挂起…
银行账户似乎是一个很好的例子来探讨事件源!
设计这个系统正如我们之前所提到的,大多数跨域通信将通过Pulsar事件流异步进行,因此即将作为微服务的Account域将如何融入整个银行生态体系:
我们的新系统将充当客户账户上任何活动的守门人。主要涉及转账的上游服务将发出它们的事件,我们的账户服务将监听这些流并作出相应反应,例如,验证信用并发出诸如AccountCredited的事件,通知那些现在变成了下游的服务账户已获得信用,这样它们就可以继续进行下一步。
其他系统也可能响应这些事件序列,例如:连接到不同数据接收端或对这些事件感兴趣的其他银行系统。
记住,虽然政策是使用事件来进行通信,但我们也可以有一些同步操作,例如,在我们的情况下,我们提供账户的创建、关闭或余额查询可通过HTTP REST接口。
但事件溯源在哪里? 等等!让我们仔细看一下这个服务本身:
在图的中心,作为整个银行账户解决方案的核心真相来源的是内部的事件流,对于每个事件流来说。
- 账户中的每个事件都是一个不可变的记录
- 流只追加新事件,新事件总是添加到末尾
- 流中的事件是按顺序出现的
最关键的是:
“每个账号都有一个事件流,因此,就有百万个事件流吧。”
如果你习惯于传统的数据库或流媒体平台,这个概念可能难以理解,但这种方法的关键原因在于可扩展性。
- 独立的流处理:每个聚合(一个账户)都有自己的流,从而可以并行处理事件,无需全局处理所有事件。
- 优化的存储性能:事件操作仅限于特定的流,通过避免对大型数据集的搜索或锁定,提高查询和更新性能。
- 简单的乐观并发控制:简单的并发控制,减少冲突,更好地与DDD聚合边界一致。
- 水平扩展:独立的流可以分布在多个服务器之间,从而使系统能够更高效地处理更多连接请求。
- 高效的事件重放:要重建聚合的状态,只需重放其特定流中的事件,避免从大型全局事件存储中搜索事件的开销。
任何如数据库投影、日志或指标等副作用,将通过订阅这些事件并作出反应来实现。
给我看看代码正如您之前在图表中所看到的,我们将使用EventStoreDB用于内部事件的存储和回放,使用Pulsar进行有界上下文之间的通信(在不同的系统之间),使用PostgreSQL用于投影,并使用Spring Boot with Kotlin构建微服务架构。
在代码架构方面,采用了六边形架构的代码库,某种形式的CQRS(命令查询职责隔离)以及事件驱动的开发理念(事件驱动(Event-Driven))。
现在,让我们更仔细地看看,以一个进入系统的SEPA转账交易为例来探讨一下,观察内部细节。
这里就有后果和副作用:
这里有一些关于使用事件驱动的架构的想法,同时也聊聊我们上面讨论的设计。
- 根据具体需求选择使用: 事件溯源可能带来显著的复杂性,如果确实符合你特定的技术和业务需求,则可以使用;否则,一个更简单的方案可能在实现你的目标的同时减少技术负担。
- 快照/投影: 如果你的事件流变得很大,可以捕获聚合的当前状态或创建专门的读模型,减少从头开始重新播放所有事件的需要。
- 设计为最终一致性:对于异步系统,设计你的系统以容忍暂时的不一致。你可能需要引入临时状态、补偿操作或诸如重试等错误处理机制。
- 乐观锁:确保在并发环境中安全地更新事件流,可以使用乐观锁而不是悲观锁(EventStoreDB提供这种能力)。
- 幂等性:几乎所有流平台,如EventStoreDb或Pulsar,默认都是“至少一次传递”,确保你的事件处理器和其他有界上下文能够处理相同的事件多次而不产生副作用。
- CQRS: 将读操作和写操作分开,以提高性能和可扩展性。使用事件溯源数据库(如EventStoreDB)存储事件,还可以使用另一个数据库进行优化查询。
- 平台化事件摄入以扩展:不要让上游依赖你为其事件编写消费者(如SEPA服务),提供可配置的设置(例如,YAML文件),这样它们可以自行处理事件摄入。这消除了对你的依赖,使它们能够独立进步,同时促进增长和扩展。
- 选择性归档:如果系统因成本或性能问题而受影响,可以将不活跃聚合或来自非常大事件流的历史事件归档到冷存储;确保在必要时能够检索这些事件。
- 提供多个外部流:随着系统的扩展,考虑发布额外的外部主题,例如账户状态更新,带有压缩或长期保留功能。这将增强客户端处理和消费最新数据的能力。
- 跨聚合业务规则:对于跨越多个聚合的业务规则(例如,确保每个用户只有一个“主”账户),可以引入中介聚合来强制执行这些规则(例如UserAccounts)或者重新考虑聚合边界。
写这篇文章让我学到了很多关于事件溯源的知识,虽然这对整个行业来说并不新鲜,对我来说却是新的概念。事件溯源似乎是一种构建可扩展系统的有效方法,提供了一种不同的随时间变化存储和处理数据的方法;此外,它与领域驱动设计或事件驱动架构等其他方法非常契合,显得更加协调,使其成为复杂架构设计中值得考虑的一个选择。
请注意, 请再次注意,我写这篇文章是为了探索和分享。我对一些技术还不太熟悉,比如 Pulsar 和 EventStoreDB,但主要是为了探讨事件溯源。尽管我已经尽力确保准确性,但过程中仍可能存在一些错误或误解。
另外,请记住,此解决方案是专门为满足所述限制的可扩展银行账户设计的。对于其他领域,事件源可以在不同的领域以更简单的方式应用。
希望你喜欢这篇文章哦!
欢迎提供反馈和纠正意见!
资源。(注:资源)https://github.com/albertllousas/bank-account-with-event-sourcing-microservice (GitHub项目链接: 银行账户事件源微服务)
https://martinfowler.com/eaaDev/EventSourcing.html 事件溯源(Event Sourcing)的马丁·福勒的文章 (Mǎdīn Fófúdé de wénzhāng guānyú shìjiàn shùyuán)
https://dzone.com/articles/event-driven-vs-event-sourced 事件驱动与事件源驱动的比较
共同学习,写下你的评论
评论加载中...
作者其他优质文章