Раздел 26 · Data Engineering

Event sourcing, CQRS и лог как источник истины

junior~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке ksqlDB собирал таблицы из потоков событий. Сегодня перевернём привычную картину ещё сильнее. Обычно главное у сервиса это база, а события в Kafka просто уведомления о том, что в базе что-то поменялось. Но можно сделать наоборот: главными станут события, а таблицы мы будем собирать из них, когда понадобятся. У «Гонца» так устроено табло Отто, и сегодня разберём, почему это удобно.

Банковская выписка

Открой выписку по карте. В ней нет строки «остаток», которую банк переписывает после каждой покупки. Там список операций: зарплата пришла, кофе купил, за связь заплатил. Остаток банк считает из этого списка, сложив все операции с начала.

У такой выписки три полезных свойства. Ничего не теряется: видно, откуда взялся каждый рубль. Остаток можно пересчитать в любой момент, хоть на вчерашнее утро. А если завтра банк захочет показать «траты на еду за месяц», он просто пройдёт по тем же операциям ещё раз и посчитает другое.

Банк хранит операции, а остаток для него производная. Так устроен event sourcing.

Хранить факты, а не состояние

Event sourcing означает: храним не текущее состояние, а все события, которые к нему привели. Заказ order-41 в таблице выглядел бы одной строкой со статусом «доставлен». В журнале событий он выглядит так:

оффсет  ключ      событие
0       order-41  created 690
2       order-41  courier-assigned 15
3       order-41  picked-up
5       order-41  delivered

Чтобы узнать, где сейчас заказ, события проходят по порядку и применяют одно за другим: создан на 690 рублей, назначен курьер 15, забран, доставлен. Такое последовательное применение называют свёрткой. Каждое событие меняет только то, о чём сообщает: courier-assigned добавляет курьера, а сумму оставляет прежней.

Kafka для такого журнала подходит идеально. Мы видели в уроке про лог, что записи в партиции только дописываются, у каждой свой оффсет, а порядок событий одного ключа не меняется, как мы разбирали в уроке про ключи. Ключ здесь это номер заказа, поэтому история заказа лежит в одной партиции целиком и читается по порядку.

Запись отдельно, чтение отдельно

Табло Отто, отчёт о выручке и экран клиента «где мой заказ» хотят от одних и тех же событий разного. Табло нужен текущий статус каждого заказа. Отчёту сумма доставленных заказов за день. Клиенту история одного заказа с временем каждого шага.

Строить для них одну общую таблицу неудобно: что подходит табло, неудобно отчёту. Поэтому запись и чтение разделяют. Это разделение называют CQRS. Пишут события в одно место, в топик Kafka. А для чтения каждый строит свою модель для чтения, проекцию: отдельный консьюмер со своей группой читает события и складывает их так, как удобно ему.

                         ┌─ группа board   → табло статусов
сервис заказов → топик ──┼─ группа revenue → выручка за день
   order-events          └─ группа history → история заказа для клиента

Каждая проекция живёт сама по себе и ничего не знает о соседях. Новая проекция появляется без единой правки в сервисе заказов: её консьюмер просто читает топик с начала.

урок из курса

Урок из курса «Data Engineering». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.