← Назад к вопросам
Для чего нужен паттерн проектирования Event Sourcing?
2.7 Senior🔥 21 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Event Sourcing: паттерн проектирования
Event Sourcing — это архитектурный паттерн, при котором все изменения состояния приложения сохраняются как последовательность неизменяемых событий (events). Вместо сохранения текущего состояния, хранится вся история изменений.
Основная идея
Вместо того чтобы:
Счет: 1000 руб → 1500 руб → 1200 руб (сохраняем только текущее значение)
Оно сохраняет:
Создан счет (1000 руб) → Пополнение (+500 руб) → Списание (-300 руб)
Назначение Event Sourcing
1. Полная история и аудит
public class BankAccount {
private long accountId;
private List<DomainEvent> events = new ArrayList<>();
private double balance = 0;
public void deposit(double amount) {
DomainEvent event = new MoneyDepositedEvent(accountId, amount);
events.add(event);
balance += amount;
}
public void withdraw(double amount) {
DomainEvent event = new MoneyWithdrawnEvent(accountId, amount);
events.add(event);
balance -= amount;
}
// История всех транзакций доступна
public List<DomainEvent> getHistory() {
return new ArrayList<>(events);
}
}
2. Time Travel — восстановление состояния на любой момент
public class AccountRepository {
public BankAccount reconstructAtTime(long accountId, LocalDateTime time) {
BankAccount account = new BankAccount(accountId);
List<DomainEvent> events = loadEventsUntil(accountId, time);
for (DomainEvent event : events) {
account.replay(event);
}
return account;
}
}
3. Отладка и анализ
public class EventAnalyzer {
public void analyzeAccountHistory(long accountId) {
List<DomainEvent> events = eventStore.getEvents(accountId);
for (DomainEvent event : events) {
System.out.println(event.getTimestamp() + ": " + event.getDescription());
}
// Полная история всех операций
}
}
Архитектура Event Sourcing
События как основной способ хранения
// Domain Event
public abstract class DomainEvent {
protected long aggregateId;
protected LocalDateTime occurredAt;
protected int version;
public abstract void apply(AggregateRoot root);
}
public class OrderCreatedEvent extends DomainEvent {
private long orderId;
private double totalPrice;
public void apply(Order order) {
order.orderId = this.orderId;
order.totalPrice = this.totalPrice;
order.status = "CREATED";
}
}
public class PaymentProcessedEvent extends DomainEvent {
private double amount;
public void apply(Order order) {
order.isPaid = true;
order.status = "PAID";
}
}
Event Store
public class EventStore {
private List<DomainEvent> events = new ArrayList<>();
public void append(DomainEvent event) {
event.setVersion(getNextVersion());
events.add(event);
}
public List<DomainEvent> getEvents(long aggregateId) {
return events.stream()
.filter(e -> e.getAggregateId() == aggregateId)
.collect(Collectors.toList());
}
public Object reconstructAggregate(long aggregateId) {
Object aggregate = new Object();
for (DomainEvent event : getEvents(aggregateId)) {
event.apply(aggregate);
}
return aggregate;
}
}
Пример: E-commerce Order
public class Order {
private long orderId;
private List<Item> items = new ArrayList<>();
private OrderStatus status = OrderStatus.PENDING;
private double totalAmount;
private List<DomainEvent> uncommittedEvents = new ArrayList<>();
public void placeOrder(Item item, double price) {
DomainEvent event = new OrderPlacedEvent(orderId, item, price);
uncommittedEvents.add(event);
items.add(item);
totalAmount += price;
}
public void confirmPayment(double amount) {
DomainEvent event = new PaymentConfirmedEvent(orderId, amount);
uncommittedEvents.add(event);
status = OrderStatus.CONFIRMED;
}
public void shipOrder() {
DomainEvent event = new OrderShippedEvent(orderId);
uncommittedEvents.add(event);
status = OrderStatus.SHIPPED;
}
public List<DomainEvent> getUncommittedEvents() {
return new ArrayList<>(uncommittedEvents);
}
}
Преимущества Event Sourcing
1. Полная аудит-тропа
- Все изменения записываются
- Соответствие требованиям регулирования (GDPR, PCI DSS)
- Невозможно скрыть изменения
2. Восстановление и отладка
- Воспроизведение состояния на любой момент времени
- Анализ причин ошибок
- Тестирование нового функционала с реальными данными
3. Масштабируемость
- Разделение между чтением и записью (CQRS)
- Асинхронная обработка событий
- Легче обновлять представления данных
4. Гибкость
- Добавление новых представлений без изменения основных данных
- Эволюция события через версионирование
Недостатки
1. Сложность
- Требует понимания паттернов (CQRS, Saga)
- Сложнее тестировать
2. Консистентность данных
- События могут быть обработаны асинхронно
- Временная несогласованность между системами
3. Хранилище
- База растет со временем
- Нужны стратегии архивирования (snapshots)
Snapshot паттерн
public class SnapshotStore {
public void saveSnapshot(long aggregateId, int version, Object state) {
// Сохраняем снимок состояния
snapshots.put(aggregateId, new Snapshot(version, state));
}
public Order reconstructWithSnapshot(long orderId) {
Snapshot snapshot = snapshots.get(orderId);
Order order = (Order) snapshot.getState();
// Применяем только события после снимка
List<DomainEvent> recentEvents =
eventStore.getEventsSince(orderId, snapshot.getVersion());
for (DomainEvent event : recentEvents) {
event.apply(order);
}
return order;
}
}
Когда использовать
Идеален для:
- Финансовых систем (полная история обязательна)
- Систем с требованиями к аудиту
- Доменов с сложной бизнес-логикой
- CQRS приложений
Не рекомендуется для:
- Простых CRUD приложений
- Систем с критичными требованиями по производительности чтения
- Проектов с ограниченным бюджетом на сложность
Event Sourcing — мощный паттерн для систем, требующих полной истории и аудита.