Инженерия ПО
Event Sourcing и CQRS
Аудит-лог, история изменений, отладка production инцидентов, temporal queries ('какое было состояние заказа вчера в 14:32?'). Все эти задачи решаются одинаково сложно с обычными базами данных - дополнительные таблицы, триггеры, сложные запросы. Event Sourcing даёт всё это бесплатно: история - это не логирование, это primary storage.
- **LinkedIn**: CQRS с Kafka как event bus - события из write store распространяются в десятки разных read stores (поиск, рекомендации, аналитика) с разными latency требованиями
- **Axon Framework**: Java framework для Event Sourcing + CQRS используется в банках и страховых компаниях где аудит всех изменений обязателен по регуляции
- **Kafka как Event Store**: многие компании используют Kafka не только как message broker, но и как append-only event log - события хранятся неограниченно с log compaction
Event Store
Event Sourcing - паттерн где состояние системы хранится не как текущий снапшот, а как последовательность событий. Вместо UPDATE orders SET status='confirmed' WHERE id=1 - сохраняется событие OrderConfirmed{orderId: 1, timestamp: ..., confirmedBy: ...}. Текущее состояние - это всегда результат воспроизведения (replay) всех событий в правильном порядке.
Event Store - append-only хранилище событий. Операции: Append (добавить событие в конец стрима) и Read (прочитать события с позиции N). Никакого UPDATE или DELETE. EventStoreDB, Kafka (как event log), или таблица PostgreSQL с sequence номером. Ключевое свойство: события неизменяемы. Если событие записано - оно там навсегда. Исправление ошибки - новое событие, не правка старого.
В Event Sourcing пользователь изменил email. Как это правильно сохранить?
Projections
Projection (проекция) - производное представление данных, построенное из событий. Из одного и того же набора событий можно построить разные проекции: Current State (текущее состояние заказа), Order History (полная история изменений), Analytics Dashboard (агрегированная статистика). Проекции пересчитываются из событий - их можно удалить и восстановить, добавить новую проекцию для данных которые уже есть в системе.
Replay - воспроизведение всех событий с начала для построения или обновления проекции. Снапшоты оптимизируют replay: вместо воспроизведения 10000 событий - загружается снапшот с позиции 9000 и воспроизводятся последние 1000. EventStoreDB создаёт снапшоты автоматически. Checkpoint - позиция в event stream до которой проекция обработана - позволяет продолжить с места остановки при перезапуске.
Нужна новая аналитическая проекция для данных существующих в системе 2 года. Как это делается в Event Sourcing?
CQRS
CQRS (Command Query Responsibility Segregation) - разделение операций записи (Commands) и чтения (Queries) на разные модели. Идея Грега Янга и Бертрана Мейера: одна и та же модель плохо оптимизирована одновременно для записи (нормализованные данные, бизнес-логика, транзакции) и чтения (денормализованные проекции, join'ы, аналитика).
Write side: принимает Commands (CreateOrder, AddItem, Confirm), валидирует, применяет бизнес-логику, сохраняет события. Read side: отдельная база данных (или несколько), оптимизированная для конкретных запросов. Elasticsearch для full-text search, Redis для dashboard, PostgreSQL с материализованными views для reports. Синхронизация через события: Command -> Event -> Update Read Model. Масштабирование read и write независимо.
В CQRS система Command обновляет write store, но read store обновляется асинхронно через события. Пользователь создал заказ и сразу запросил список заказов - нового заказа нет в списке. Это...
Eventual Consistency
Eventual Consistency - гарантия что при отсутствии новых обновлений все реплики данных рано или поздно достигнут одного состояния. Werner Vogels (Amazon CTO) в 2008 году: 'Eventually consistent - не слабая консистентность, а понимание что ACID everywhere невозможно без жертв доступностью и латентностью'. CAP теорема: в распределённой системе нельзя одновременно обеспечить Consistency + Availability + Partition Tolerance.
В Event Sourcing и CQRS eventual consistency - естественное состояние. События публикуются в bus (Kafka), projection handlers подписываются, обновляют свои read stores. Lag (задержка) измеряется в миллисекундах в нормальных условиях. При сбое - восстановление из event log. Idempotency ключевой принцип: обработчик события должен быть idempotent - повторная обработка одного события не должна создавать дублей.
Event Sourcing и CQRS - это всегда одно целое и их нельзя использовать отдельно
Event Sourcing и CQRS - независимые паттерны. CQRS можно применять без Event Sourcing (просто разные модели для чтения и записи). Event Sourcing усиливает CQRS, но не требует его
CQRS решает проблему разных требований к read и write моделям. Event Sourcing решает проблему хранения истории изменений. Оба полезны отдельно, часто применяются вместе для взаимного усиления
Event handler получает одно и то же событие OrderShipped дважды (из-за retry Kafka). Как idempotency защищает проекцию?
Связанные темы
Event Sourcing и CQRS естественно связаны с другими паттернами:
- Domain-Driven Design — Domain Events из DDD - строительный материал для Event Sourcing. Bounded Contexts интегрируются через события
- Микросервисы: паттерны — Saga pattern для distributed transactions использует события - прямое применение Event Sourcing для координации между сервисами
Вопросы для размышления
- Event Sourcing усложняет систему. Для каких доменов это оправдано, а для каких CRUD достаточно?
- Как обрабатывать версионирование событий: схема события изменилась, а старые события уже записаны в store?
- При каком объёме событий производительность replay становится проблемой и как её решают снапшоты?