Открыть сервис

Event sourcing

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

История

Концепция event sourcing восходит к идеям событийно-ориентированного программирования и систем управления версиями, которые появились в 1980-х годах. Одним из ранних примеров является система Lotus Notes (1989), где документы хранили историю изменений. В 1990-х годах паттерн получил развитие в области баз данных с поддержкой временных рядов (temporal databases) и в системах аудита.

Термин «event sourcing» был введён в 2005 году Мартином Фаулером в его статье на сайте martinfowler.com, где он описал этот подход как альтернативу традиционному хранению состояния. Популяризации способствовали работы Грега Янга (Greg Young) и Эрика Эванса (Eric Evans), автора книги «Domain-Driven Design» (2003). В 2010-х годах event sourcing стал широко применяться в микросервисной архитектуре и системах с высокой нагрузкой, таких как финансовые платформы, системы управления заказами и игровые серверы.

Основные принципы

Событие как источник истины

В event sourcing единственным источником достоверных данных является журнал событий (event log). Каждое событие представляет собой факт, который уже произошёл и не может быть изменён или удалён. Например, событие «Заказ создан» содержит дату, идентификатор заказа и состав товаров. Если заказ был отменён, добавляется новое событие «Заказ отменён», а не удаляется или изменяется предыдущее.

Неизменяемость событий

События записываются в журнал однократно и никогда не редактируются. Это обеспечивает аудиторский след (audit trail) и возможность отката к любому моменту времени. При необходимости исправления ошибки (например, неверная цена) добавляется корректирующее событие, а не изменяется исходное.

Восстановление состояния

Текущее состояние объекта (агрегата) вычисляется путём последовательного применения всех событий, относящихся к этому объекту, начиная с пустого состояния. Этот процесс называется воспроизведением событий (event replay). Для оптимизации производительности часто используется снэпшот (snapshot) — сохранённое состояние на определённый момент времени, с которого начинается воспроизведение.

Архитектура и компоненты

Хранилище событий (Event Store)

Специализированная база данных или модуль, предназначенный для хранения событий. Основные требования: поддержка добавления событий (append-only), высокая производительность записи и возможность чтения по идентификатору агрегата или по типу события. Примеры: EventStoreDB (Greg Young), Kafka (как брокер сообщений), PostgreSQL с использованием таблиц для событий.

Агрегат

Объект предметной области, который инкапсулирует состояние и бизнес-логику. В event sourcing агрегат не хранит состояние напрямую, а генерирует события при вызове команд. Например, агрегат «Заказ» может обработать команду «Добавить товар» и создать событие «Товар добавлен в заказ».

Проекции (Projections)

Механизмы, которые читают события из журнала и строят оптимизированные представления данных (read models) для быстрого чтения. Проекции могут быть материализованными (сохраняются в отдельной базе данных) или вычисляемыми на лету. Например, для отображения списка заказов пользователя создаётся проекция, которая агрегирует события по каждому заказу.

Команды и события

  • Команда — запрос на выполнение действия (например, «Создать заказ»). Команда может быть отклонена, если нарушены бизнес-правила.
  • Событие — факт, который уже произошёл (например, «Заказ создан»). Событие не может быть отклонено, так как оно уже зафиксировано.

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

  • Полный аудит: возможность восстановить всю историю изменений, что полезно для соответствия требованиям регуляторов (например, в финансах и здравоохранении).
  • Временные срезы: возможность восстановить состояние системы на любой момент времени, что упрощает отладку и анализ инцидентов.
  • Гибкость в построении read-моделей: проекции могут быть перестроены заново при изменении требований, без необходимости миграции данных.
  • Устойчивость к ошибкам: при сбое можно восстановить состояние из журнала событий.
  • Поддержка событийно-ориентированной архитектуры (EDA): события могут быть опубликованы в брокеры сообщений для интеграции с другими сервисами.

Недостатки и ограничения

  • Сложность реализации: требуется глубокое понимание предметной области и архитектурных принципов.
  • Производительность: при большом количестве событий воспроизведение может быть медленным, хотя снэпшоты частично решают эту проблему.
  • Управление схемой событий: изменение структуры событий (например, добавление нового поля) требует версионирования и миграции существующих событий.
  • Хранение данных: журнал событий может занимать значительный объём, особенно в системах с высокой частотой изменений.
  • Отсутствие стандартных инструментов: многие реляционные базы данных не оптимизированы для append-only хранения, что требует дополнительной настройки.

Применение

Event sourcing используется в системах, где важны аудит, история изменений и возможность восстановления состояния:

  • Финансовые системы: учёт транзакций, обработка платежей, управление счетами (например, банковские системы).
  • Электронная коммерция: управление заказами, корзинами, скидками и возвратами.
  • Системы управления версиями: Git, Mercurial — по сути, реализуют event sourcing для файлов.
  • Игровая индустрия: сохранение состояния игры, восстановление после сбоев, реплеи.
  • Логистика и цепочки поставок: отслеживание статусов заказов, перемещений товаров.
  • Здравоохранение: ведение медицинских карт с историей изменений.

Пример реализации

Рассмотрим упрощённый пример на языке Python (без фреймворков). Допустим, есть система управления заказами.

```python class Event: def __init__(self, aggregate_id, event_type, data, timestamp): self.aggregate_id = aggregate_id self.event_type = event_type self.data = data self.timestamp = timestamp

class EventStore: def __init__(self): self.events = []

def append(self, event): self.events.append(event)

def get_events(self, aggregate_id): return [e for e in self.events if e.aggregate_id == aggregate_id]

class OrderAggregate: def __init__(self, event_store): self.event_store = event_store self.state = {}

def apply_event(self, event): if event.event_type == 'OrderCreated': self.state = {'id': event.data['id'], 'items': []} elif event.event_type == 'ItemAdded': self.state['items'].append(event.data['item'])

def create_order(self, order_id): event = Event(order_id, 'OrderCreated', {'id': order_id}, time.time()) self.event_store.append(event) self.apply_event(event)

def add_item(self, order_id, item): event = Event(order_id, 'ItemAdded', {'item': item}, time.time()) self.event_store.append(event) self.apply_event(event)

def get_state(self, order_id): for event in self.event_store.get_events(order_id): self.apply_event(event) return self.state ```

В этом примере EventStore хранит все события, а OrderAggregate восстанавливает состояние путём применения событий.

Связь с другими паттернами

Event sourcing часто сочетается с CQRS (Command Query Responsibility Segregation) — разделением команд и запросов. В такой архитектуре команды записывают события в хранилище, а запросы читают из проекций. Также event sourcing может использоваться с Domain-Driven Design (DDD), где агрегаты являются основными единицами.

Критика

Критики отмечают, что event sourcing увеличивает сложность разработки и эксплуатации, особенно в небольших проектах. Кроме того, при неправильной реализации может возникнуть проблема «событийного ада» (event storming), когда количество событий становится слишком большим для эффективного управления. Некоторые разработчики считают, что для большинства приложений достаточно традиционного CRUD с аудитом, а event sourcing оправдан только в системах с высокими требованиями к аудиту и временным срезам.

Источники

  • Martin Fowler. «Event Sourcing» (2005). martinfowler.com.
  • Greg Young. «CQRS and Event Sourcing» (2010). CodeBetter.
  • Eric Evans. «Domain-Driven Design: Tackling Complexity in the Heart of Software» (2003). Addison-Wesley.
  • Vaughn Vernon. «Implementing Domain-Driven Design» (2013). Addison-Wesley.
  • EventStoreDB documentation. eventstore.com.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →