文章目录
- 【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;
}
init
和startThread
方法确保线程得到正确初始化和运行。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
八、事件溯源模式的好处和权衡
好处:
- 可审计性:记录对状态的每次更改,允许进行全面审计。
- 可重放性:事件可以被重新处理以重新创建历史状态或移动到新状态。
- 可扩展性:事件可以异步和并行处理。
权衡:
- 复杂性:实现和维护事件溯源系统可能会引入额外的复杂性。
- 事件存储大小:存储每个状态变化可能会导致数据量很大。
- 事件版本控制:随着时间的推移,事件结构的变化需要仔细处理,以确保系统的完整性。