【Java设计模式】事件溯源模式

文章目录

  • 【Java设计模式】事件溯源模式
    • 一、概述
    • 二、别名
    • 三、事件溯源设计模式的意图
    • 四、通过实际示例详细解释事件溯源模式
    • 五、Java中事件溯源模式的编程示例
    • 六、何时在Java中使用事件溯源模式
    • 七、事件溯源模式在Java中的实际应用
    • 八、事件溯源模式的好处和权衡
    • 九、源码下载

【Java设计模式】事件溯源模式

一、概述

事件溯源是一种设计模式,主张将状态变化存储为一系列事件。它不是更新数据库中的记录,而是将所有变化存储为单独的事件,通过重放这些事件,可以在任何时间点重新创建应用程序的状态。

二、别名

  • 事件日志
  • 事件流

三、事件溯源设计模式的意图

事件溯源设计模式旨在将状态变化存储为一系列事件。它通过将所有更改存储为单独的事件,而不是直接更新数据库中的记录,使得在任何时间点都可以通过重放这些事件来重新创建应用程序的状态。

四、通过实际示例详细解释事件溯源模式

实际示例:

考虑一个银行应用程序,它跟踪用户账户的所有交易。在这个系统中,每笔存款、取款和转账都被记录为事件日志中的一个单独事件。而不是简单地更新当前账户余额,每笔交易都被存储为一个离散事件。这种方法允许银行维护所有账户活动的完整且不可变的历史记录。如果出现差异,银行可以重放事件序列来重建任何时间点的账户状态。这提供了强大的审计跟踪,便于调试,并支持事务回滚和历史数据分析等功能。
通俗解释:
事件溯源将所有状态变化记录为一系列不可变事件,以确保可靠的状态重建和可审计性。
微软的文档说:
事件溯源模式定义了一种处理数据操作的方法,该方法由一系列事件驱动,每个事件都记录在一个仅追加的存储中。应用程序代码将一系列事件发送到事件存储中,这些事件强制描述了数据上发生的每个操作,然后这些事件被持久化。每个事件代表对数据的一组更改(例如AddedItemToOrder)。

五、Java中事件溯源模式的编程示例

在编程示例中,我们在银行账户之间进行一些资金转账。
Event类管理事件队列并控制异步处理的线程操作。每个事件都可以看作是影响系统状态的状态变化。

public class Event {private static final Event INSTANCE = new Event();private static final int MAX_PENDING = 16;private int headIndex;private int tailIndex;private volatile Thread updateThread = null;private final EventMessage[] pendingEvents = new EventMessage[MAX_PENDING];Event() {}public static Event getInstance() {return INSTANCE;}
}

triggerEvent方法是创建事件的地方。每次触发事件时,它都会被创建并添加到队列中。这个事件包含了状态变化的详细信息。

public void triggerEvent(EventMessage eventMessage) {init();for(var i = headIndex; i!= tailIndex; i = (i + 1) % MAX_PENDING) {var pendingEvent = getPendingEvents()[i];if(pendingEvent.equals(eventMessage)) {return;}}getPendingEvents()[tailIndex] = eventMessage;tailIndex = (tailIndex + 1) % MAX_PENDING;
}

initstartThread方法确保线程得到正确初始化和运行。stopService方法用于在不再需要时停止线程。这些方法管理用于处理事件的线程的生命周期。

public synchronized void stopService() throws InterruptedException {if(updateThread!= null) {updateThread.interrupt();updateThread.join();updateThread = null;}
}
public synchronized boolean isServiceRunning() {return updateThread!= null && updateThread.isAlive();
}
public void init() {if(updateThread == null) {updateThread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {update();}});startThread();}
}
private synchronized void startThread() {if (!updateThread.isAlive()) {updateThread.start();headIndex = 0;tailIndex = 0;}
}

该示例由App类及其main方法驱动。

@Slf4j
public class App {public static final int ACCOUNT_OF_DAENERYS = 1;public static final int ACCOUNT_OF_JON = 2;public static void main(String[] args) {var eventProcessor = new DomainEventProcessor(new JsonFileJournal());LOGGER.info("运行系统第一次............");eventProcessor.reset();LOGGER.info("创建账户............");eventProcessor.process(new AccountCreateEvent(0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen"));eventProcessor.process(new AccountCreateEvent(1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow"));LOGGER.info("进行一些资金操作............");eventProcessor.process(new MoneyDepositEvent(2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000")));eventProcessor.process(new MoneyDepositEvent(3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100")));eventProcessor.process(new MoneyTransferEvent(4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS,ACCOUNT_OF_JON));LOGGER.info("...............状态:............");LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString());LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString());LOGGER.info("在那时系统关闭,内存中的状态被清除............");AccountAggregate.resetState();LOGGER.info("通过日志文件中的事件恢复系统............");eventProcessor = new DomainEventProcessor(new JsonFileJournal());eventProcessor.recover();LOGGER.info("...............恢复的状态:............");LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString());LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString());}
}

运行示例将产生以下控制台输出。

22:40:47.982 [main] INFO com.iluwatar.event.sourcing.app.App -- 运行系统第一次............
22:40:47.984 [main] INFO com.iluwatar.event.sourcing.app.App -- 创建账户............
22:40:47.985 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.089 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.090 [main] INFO com.iluwatar.event.sourcing.app.App -- 进行一些资金操作............
22:40:48.090 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.095 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- 这里可以调用一些仅用于实时执行的外部API。
22:40:48.101 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............状态:............
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000}
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100}
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- 在那时系统关闭,内存中的状态被清除............
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- 通过日志文件中的事件恢复系统............
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............恢复的状态:............
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000}
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100}

在这个示例中,通过重放队列中的事件,可以在任何点重新创建系统的状态。这是事件溯源模式的一个关键特性。

六、何时在Java中使用事件溯源模式

  • 在系统中,完整的审计跟踪和历史变化至关重要。
  • 在复杂领域中,应用程序的状态源自一系列变化。
  • 对于受益于高可用性和可扩展性的系统,因为事件溯源自然适用于分布式系统。

七、事件溯源模式在Java中的实际应用

  • 金融系统,用于跟踪交易和账户余额随时间的变化。
  • 电子商务应用程序,用于订单和库存管理。
  • 实时数据处理系统,其中事件一致性和可重放性至关重要。
  • The LMAX Architecture

八、事件溯源模式的好处和权衡

好处:

  • 可审计性:记录对状态的每次更改,允许进行全面审计。
  • 可重放性:事件可以被重新处理以重新创建历史状态或移动到新状态。
  • 可扩展性:事件可以异步和并行处理。

权衡:

  • 复杂性:实现和维护事件溯源系统可能会引入额外的复杂性。
  • 事件存储大小:存储每个状态变化可能会导致数据量很大。
  • 事件版本控制:随着时间的推移,事件结构的变化需要仔细处理,以确保系统的完整性。

九、源码下载

事件溯源模式示例代码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/52802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何清理Linux旧内核并设置默认内核版本

文章目录 1. 引言2. 检查和清理旧内核2.1 检查 /boot 目录中的残留文件2.2 手动删除与旧内核相关的文件2.3 更新 GRUB 配置2.4 清理旧内核包(可选) 3. 安装并保留特定内核版本3.1 安装内核版本 5.15.0-1193.2 删除其他不需要的内核版本 4. 设置默认内核版…

内存管理篇-16二级页表工作原理

1.修正上节课的转换图 上节课的页表的一级页表其实并不完全正确,一般虚拟页帧和物理页帧号不会都占用实际字段,这样毕竟很浪费内存。 2.再分析一下页表的开销情况: 一级页表:以4KB物理页为映射单位,每个进程4MB的虚…

动态读取nacos中修改的项目配置文件

本项目用的还是springboot项目,咱们直接上代码 一:首先看下nacos中需要动态获取的属性 二:把需要动态读取的配置类中的属性整理一个实体类 mport lombok.Data; import org.springframework.boot.context.properties.ConfigurationPropert…

Python酷库之旅-第三方库Pandas(114)

目录 一、用法精讲 501、pandas.DataFrame.mode方法 501-1、语法 501-2、参数 501-3、功能 501-4、返回值 501-5、说明 501-6、用法 501-6-1、数据准备 501-6-2、代码示例 501-6-3、结果输出 502、pandas.DataFrame.pct_change方法 502-1、语法 502-2、参数 502…

Django 第十一课 -- ORM - 多表实例

目录 一. 前言 二. 创建模型 三. 插入数据 四. ORM - 添加数据 4.1. 一对多(外键 ForeignKey) 4.2. 多对多(ManyToManyField):在第三张关系表中新增数据 4.3. 关联管理器(对象调用) 五. ORM 查询 5.1. 一对多 5.2. 一对一 5.3. 多对多 六. 基于双下划线…

SprinBoot+Vue实验室考勤管理小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平…

【机器学习】数据预处理-特征工程与特征选择

目录 一、特征工程 二、数据变换 1.变换 2.归一化 三、数据清洗 1.异常数据 2.数据清洗 四、特征选择 1.Filter过滤法 2.Wrapper包裹法 ... 3.Embedded嵌入法 ... 五、降维算法 1.SVD 2.PCA 一、特征工程 特征工程就是从原始数据提取特征的过程,这些…

问:Java中++i和i++的区别?

在Java中,i(前缀增量操作符)和i(后缀增量操作符)都用于增加变量i的值,但它们在表达式中的行为有所不同。主要区别在于它们的值以及它们在表达式中的副作用何时发生。 前缀增量操作符 i 作用:先…

Leetcode3243. 新增道路查询后的最短距离 I

Every day a Leetcode 题目来源:3243. 新增道路查询后的最短距离 I 解法1:广度优先搜索 暴力。 每次加边后重新跑一遍 BFS,求出从 0 到 n−1 的最短路。 代码: /** lc appleetcode.cn id3243 langcpp** [3243] 新增道路查询…

【STM32】BKP备份寄存器与RTC实时时钟

本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发 目录 BKP简介 BKP代码注解 读写备份寄存器 复位备份寄存器 BKP代码 RTC简介 RTC代码注解 RTCCLK时钟源选择 分频器配置 时钟同步 RTC代码 MyRTC.h MyRTC.c main.c BKP简介 BKP&…

数学建模--K-Means聚类分析

目录 1.聚类分析步骤 1.1简单介绍 1.2两个概念 1.3几种距离 1.4更新质心 1.5终止条件 2.归一化处理 3.肘部法则 4.搭建K-Means分析模型 5.选择最佳K值 6.绘制3D图形 1.聚类分析步骤 1.1简单介绍 K-Means聚类分析是属于聚类分析的一种,这个数据机器学习的…

如何用GPT进行编程辅助?

随着人工智能技术的迅速发展,GPT(生成型预训练模型)已成为开发者的得力助手之一。无论是编写代码、调试、生成文档,还是解决编程问题,GPT都能提供显著帮助。这篇教程将详细介绍如何使用GPT进行编程辅助,并提…

批量进行Mysql数据处理的一项工作记录以及保存一个nginx变量大全

一、批量进行Mysql数据处理的一项工作记录 在使用SQL执行一起数据批量处理的时候遇到执行数速度非常慢。表temp_users是一个包含百万级的用户ID表,表user_list是一个亿级的表,因为跨库,这里使用的是federated引擎创建的结构表。根据要实现的目…

每日一练【最大连续1的个数 III】

一、题目描述 给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。 二、题目解析 本题同样是利用滑动窗口的解法。 首先进入窗口,如果是1,就直接让right,但是如果是…

python网络爬虫(三)——爬虫攻防

爬虫是模拟人的浏览访问行为,进行数据的批量抓取,当抓取的数据量逐渐增大时,会给被访问的服务器造成很大的压力,甚至有可能崩溃。换句话说就是,服务器是不喜欢有人抓取自己的数据的,那么,网站方…

SAP LE学习笔记07 - MM与WM跨模块收货到仓库的流程中 如何实现 先上架再入库

上一章讲了LE中收货的一些特殊情况: 1,MM模块收货时,特别移动指标来标识的物料直接产生TO 2,MM中直接收货到仓库的固定Storage Bin(棚番)上 SAP LE学习笔记06 - MM与WM跨模块收货到仓库的流程中 带特别移动指标的物料也可以直接…

常用Numpy操作(笔记整理)

目录 一、常用(自查) 1. 创建数组(array) 2. 数组形状(shape) 3. 数组维度(ndim) 4. 数组⼤⼩(size) 5. 数组数据类型(dtype) …

etcd备份恢复操作

etcd集群每个节点上的数据都是相同的,在任意一个正常的节点上备份都能得到8s集群完整的数据 Kubernetes 集群备份主要是备份 ETCD 集群,而恢复时,主要考虑恢复整个顺序: 1,停止所有 Master 上 kube-apiserver 服务 2&…

nuxt3模拟手机验证码

文章目录 前言前端后端前面代码会出现的问题约束button的小插件连接mongodb来写登陆项目开源链接前言 真实应该要连接短信验证码服务,但是众所周知所有的服务和IT都是靠服务来挣钱的,所以我们目前只能模拟手机验证码登陆 考虑到账号的唯一值就想到了手机和验证码(基本上的网站…

Netflix Feign:微服务HTTP调用如何简化?

Netflix Feign:微服务HTTP调用如何简化? 1、什么是Netflix Feign?2、Feign的优点3、示例4、总结 💖The Begin💖点点关注,收藏不迷路💖 1、什么是Netflix Feign? Feign是一个声明式的…