Event sourcing, CQRS и лог как источник истины
Урок про архитектурные паттерны, в которых Kafka из транспорта становится сердцем системы — хранилищем правды.
Event sourcing — хранение состояния не как текущего снимка, а как полной последовательности событий-изменений; текущее состояние выводится их проигрыванием. CQRS разделяет модель записи и модель чтения.
Зачем это нужно
В классической схеме БД хранит текущее состояние: balance = 500. Как оно стало таким — потеряно. В event sourcing вы храните события: +1000, -300, -200, а баланс получаете их сложением. Вы не теряете историю, можете воспроизвести состояние на любой момент и переиграть его при изменении логики. Kafka с её неизменяемым логом — естественное хранилище для этого.
Event sourcing на логе
Лог событий счёта acc-7:
[deposit +1000][withdraw -300][withdraw -200]
| проигрываем (свёртка)
v
Текущее состояние: balance = 500
(на любой момент: остановись на нужном оффсете)
Состояние — производная лога. Хотите баланс «на вчера» — сверните события до вчерашнего оффсета. Поменяли правило начисления — пересоберите состояние, переиграв лог.
CQRS: разделение записи и чтения
CQRS (Command Query Responsibility Segregation) разводит две стороны: запись принимает команды и порождает события в лог; чтение строит из лога удобные для запросов проекции (read models) — каждую под свой сценарий.
Команды -> [модель записи] -> события в Kafka
|
+--------------------+--------------------+
v v v
проекция "баланс" проекция "история" проекция "поиск"
(KTable) (ClickHouse) (индекс)
Одна правда (лог) — много представлений, каждое оптимизировано под свои запросы. Новую проекцию добавляют, перечитав лог с начала, не трогая запись.
Стриминговый ETL
Тот же принцип в дата-инженерии: лог — источник истины, а проекции (витрины в хранилище, поисковые индексы) строятся стримингом (Streams/ksqlDB/Connect) и обновляются в реальном времени, а не ночным батчем. Это и есть «стриминговый ETL»: трансформация на лету по мере событий.
Как работает под капотом
Связка event sourcing + CQRS на Kafka опирается на свойства, которые мы уже разобрали: неизменяемость лога даёт надёжную историю, replay позволяет перестроить любую проекцию, компакция держит снимок текущего состояния по ключу, а Streams/Connect материализуют проекции. Цена паттерна — eventual consistency между проекциями (read model отстаёт от записи на время обработки) и необходимость версионировать схему событий (старые события в логе живут вечно — их формат менять нельзя, только добавлять новые типы).
Частые ошибки
- Event sourcing везде. Паттерн дорог в сложности; применяйте там, где история и аудит реально нужны.
- Менять формат старых событий. В логе они вечны; эволюционируйте схему совместимо (см. Schema Registry).
- Ждать мгновенной согласованности read model. Проекции отстают; проектируйте под eventual consistency.
Итоги
- Event sourcing хранит изменения как события; состояние — их свёртка, воспроизводимая на любой момент.
- CQRS разделяет запись (события в лог) и чтение (проекции под запросы); проекций может быть много.
- Лог — единый источник истины; проекции строятся стримингом и обновляются в реальном времени.