Вопрос проверяет понимание паттерна Event Sourcing, который используется для хранения состояния приложения как последовательности событий, и его преимуществ для аудита и восстановления состояния.
Event Sourcing — это архитектурный паттерн, при котором все изменения состояния приложения сохраняются в виде последовательности событий. Вместо хранения только текущего состояния (как в традиционных CRUD-системах) система записывает каждое действие пользователя или системы как отдельное, неизменяемое событие. Состояние объекта затем может быть восстановлено путём применения ("воспроизведения") всех сохранённых событий, начиная с самого первого.
Основная идея заключается в том, что события — это источник истины. Это даёт несколько важных преимуществ:
Event Sourcing часто используется в системах, где важна точность, аудит и возможность отката: банковские операции, системы управления заказами (e-commerce), трекинг изменений в документах, многопользовательские игры (для реплеев). Он редко применяется изолированно и часто комбинируется с паттерном CQRS (Command Query Responsibility Segregation) для разделения моделей записи (команд, генерирующих события) и чтения (проекций).
Рассмотрим простейший пример банковского счёта. Вместо хранения только текущего баланса, мы храним историю операций.
// События — это простые объекты с данными.
class AccountCreatedEvent {
constructor(accountId, initialBalance) {
this.type = 'AccountCreated';
this.accountId = accountId;
this.balance = initialBalance;
this.timestamp = new Date();
}
}
class MoneyDepositedEvent {
constructor(accountId, amount) {
this.type = 'MoneyDeposited';
this.accountId = accountId;
this.amount = amount;
this.timestamp = new Date();
}
}
class MoneyWithdrawnEvent { /* аналогично */ }
// Агрегат (Account) применяет события для изменения своего состояния.
class Account {
constructor(events = []) {
this.balance = 0;
this.changes = []; // Новые, ещё не сохранённые события
// Восстанавливаем состояние из истории событий
events.forEach(event => this.apply(event, true));
}
apply(event, isFromHistory = false) {
switch(event.type) {
case 'AccountCreated':
this.balance = event.balance;
break;
case 'MoneyDeposited':
this.balance += event.amount;
break;
case 'MoneyWithdrawn':
this.balance -= event.amount;
break;
}
if (!isFromHistory) {
this.changes.push(event); // Запоминаем новое событие
}
}
deposit(amount) {
this.apply(new MoneyDepositedEvent(this.id, amount));
}
// Метод для получения всех новых событий для сохранения
getUncommittedChanges() {
return this.changes;
}
}
// Использование:
// 1. Восстановление счёта из Event Store
const pastEvents = [
new AccountCreatedEvent('acc-1', 100),
new MoneyDepositedEvent('acc-1', 50)
];
const account = new Account(pastEvents);
console.log(account.balance); // 150
// 2. Выполнение новой операции
account.withdraw(30);
console.log(account.balance); // 120
// 3. Сохранение новых событий в хранилище
const newEvents = account.getUncommittedChanges();
// saveToEventStore(newEvents);В этом примере состояние счёта (баланс) вычисляется динамически из событий. Для получения текущего баланса не нужно делать запрос в таблицу "accounts", достаточно применить все события к изначально пустому объекту.
Event Sourcing стоит применять в системах, где критически важны полная история изменений, аудит, возможность отката или построения различных представлений данных. Он добавляет сложность (необходимость хранилища событий, обработки потока, возможных конфликтов), поэтому не подходит для простых CRUD-приложений. Идеальная область — сложные предметные области с чётко выраженными событиями, такие как финансы, логистика или игровая механика.