← Назад к вопросам

Для чего нужен паттерн проектирования 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 — мощный паттерн для систем, требующих полной истории и аудита.

Для чего нужен паттерн проектирования Event Sourcing? | PrepBro