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