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 разделяет запись (события в лог) и чтение (проекции под запросы); проекций может быть много.
  • Лог — единый источник истины; проекции строятся стримингом и обновляются в реальном времени.
Проверьте себя
1. Что хранит event sourcing вместо текущего состояния?
AТолько последний снимок
BПолную последовательность событий-изменений, из которых выводится состояние
CРезервные копии БД
DЛоги приложения
2. Что разделяет CQRS?
AПартиции и реплики
BМодель записи (команды -> события) и модель чтения (проекции под запросы)
CПродюсеров и брокеров
DТопики и группы
3. Почему формат старых событий нельзя менять?
AKafka это запрещает технически
BСобытия в логе живут вечно (replay), поэтому схему эволюционируют только совместимо
CЭто замедляет чтение
DФормат не влияет ни на что