Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

AI info

Карта сайта

Документы

Медиа

Назад
Вопрос про Java: Event Sourcing, CQRS, domain events, event store, event-driven architecture

Что такое Event Sourcing и в чем его идея?

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

Короткий ответ

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

Длинный ответ

Event Sourcing — это архитектурный паттерн, при котором все изменения состояния приложения сохраняются в виде последовательности событий. Вместо хранения только текущего состояния (как в традиционных CRUD-системах) система записывает каждое действие пользователя или системы как отдельное, неизменяемое событие. Состояние объекта затем может быть восстановлено путём применения ("воспроизведения") всех сохранённых событий, начиная с самого первого.

Ключевая идея и преимущества

Основная идея заключается в том, что события — это источник истины. Это даёт несколько важных преимуществ:

  • Полный аудит и трассируемость: Поскольку каждое изменение зафиксировано, можно точно узнать, что, когда и кем было изменено.
  • Воспроизведение состояния: Можно восстановить состояние системы на любой момент времени в прошлом, что критично для финансовых систем, игр или аналитики.
  • Гибкость: Можно создавать новые "проекции" (представления) данных, обрабатывая поток событий по-новому, без изменения основной логики записи.
  • Поддержка сложной доменной логики: Паттерн хорошо сочетается с Domain-Driven Design (DDD), где события часто соответствуют доменным событиям.

Как и где применяется

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

Уровень

  • Рейтинг:

    4

  • Сложность:

    7

Навыки

  • Java

    Java

  • Spring

    Spring

Ключевые слова

#Event Sourcing

#CQRS

#domain events

#event store

#event-driven architecture

Подпишись на Java Developer в телеграм