Назад к вопросам
Senior
147
questionbank

Что такое шаблон Event Sourcing?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Шаблон Event Sourcing — это подход к построению приложений, в котором все изменения состояния системы сохраняются как последовательность неизменяемых событий. Вместо хранения текущего состояния объекта, сохраняется полный лог действий, которые привели к этому состоянию.

Ключевые особенности:

  • Неизменяемость событий: Однажды созданное событие никогда не изменяется и не удаляется.
  • Последовательность: События сохраняются в строгом хронологическом порядке.
  • Архив изменений: Полная история всех изменений доступна для аудита, отладки и анализа.
  • Восстановление состояния: Текущее состояние объекта может быть построено путем "воспроизведения" всех применимых событий с начала времен или определенной точки.

Преимущества:

  • Неограниченное ретроспективное наблюдение: Легко увидеть, как система пришла к текущему состоянию.
  • Аудит и отладка: Полная история событий облегчает поиск причин ошибок и аудит действий.
  • Параллельная обработка и масштабирование: Разделение записи событий от их обработки позволяет легче масштабировать приложение.
  • Поддержка CQRS (Command Query Responsibility Segregation): События часто используются для обновления read-моделей.

Недостатки:

  • Сложность реализации: Требует более сложной инфраструктуры для хранения и обработки событий.
  • Производительность при воспроизведении: Восстановление состояния из большого числа событий может быть медленным. Требуются оптимизации, такие как снимки состояния (snapshots).
  • Изменения схем событий: Эволюция схемы событий может быть сложной задачей.

Пример:

Представьте банковский счет. В Event Sourcing вместо обновления баланса напрямую, мы сохраняем события: "Депозит X", "Снятие Y". Текущий баланс вычисляется суммированием всех операций.

// Пример класса события
public class MoneyDepositedEvent {
    private final String accountId;
    private final BigDecimal amount;
    private final LocalDateTime timestamp;

    public MoneyDepositedEvent(String accountId, BigDecimal amount, LocalDateTime timestamp) {
        this.accountId = accountId;
        this.amount = amount;
        this.timestamp = timestamp;
    }

    // геттеры
}
// Пример агрегата (банковский счет), применяющего события
public class BankAccount {
    private String id;
    private BigDecimal balance;
    private List<Object> changes = new ArrayList<>(); // список непримененных событий

    public BankAccount(String id) {
        this.id = id;
        this.balance = BigDecimal.ZERO;
    }

    // Метод, генерирующий событие
    public void deposit(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        MoneyDepositedEvent event = new MoneyDepositedEvent(this.id, amount, LocalDateTime.now());
        apply(event); // Применяем событие к текущему состоянию
        changes.add(event); // Добавляем в список изменений для сохранения
    }

    // Метод, применяющий событие к состоянию агрегата
    private void apply(MoneyDepositedEvent event) {
        this.balance = this.balance.add(event.getAmount());
    }

    // Метод для восстановления состояния из истории событий
    public static BankAccount loadFromHistory(String id, List<Object> history) {
        BankAccount account = new BankAccount(id);
        for (Object event : history) {
            if (event instanceof MoneyDepositedEvent) {
                account.apply((MoneyDepositedEvent) event);
            }
            // Обработка других типов событий...
        }
        return account;
    }

    // геттеры и другие методы
}