Потоковая обработка

Event-Driven Architecture

Jay Kreps, LinkedIn Kafka и рождение современной EDA

2010 год: Greg Young формализует CQRS и Event Sourcing в серии статей и докладов на конференциях. Паттерн существовал в финансах, но впервые получил строгое определение для разработки ПО. 2011 год: LinkedIn открывает Kafka - Jay Kreps, Neha Narkhede, Jun Rao. Публикация описывает log как универсальную абстракцию для всех данных в компании. 2013 год: Jay Kreps пишет 'The Log: What every software engineer should know about real-time data's unifying abstraction' - эссе, изменившее то, как индустрия думает о потоках событий. Kafka стала инфраструктурой для event sourcing в масштабе: LinkedIn, Uber, Netflix, Airbnb.

Банковский счёт показывает баланс 1500 рублей, но клиент утверждает - вчера было 1800. В традиционной БД UPDATE перезаписал старое значение без следа. Что если бы хранилась не сумма, а каждая транзакция? Тогда можно 'перемотать' до любой точки - и баг воспроизвести, и аудит пройти, и compliance доказать.

  • **LinkedIn (2011)** - Kafka как event bus для всей компании: 700 млрд событий в день сегодня
  • **Axon Framework** - CQRS + Event Sourcing фреймворк для Java: используется в ABN AMRO, ING, Rabobank
  • **EventStoreDB** - специализированная СУБД для event sourcing: финтех, страховые компании, healthcare
  • **Confluent Platform** - коммерческий Kafka: CQRS projections через Kafka Streams и ksqlDB
  • **Git** - event sourcing для кода: каждый commit - событие, текущий файл = replay всех коммитов
  • **Stripe** - event-driven API: каждое действие генерирует webhook-событие для интеграций

Предварительные знания

  • Batch vs Stream Processing

Events - факты произошедшего

LinkedIn в 2011 году не мог справиться с нагрузкой. Сервисы вызывали друг друга по цепочке - ProfileService -> RecommendationService -> NotificationService. Один сервис тормозит - вся цепочка встаёт. Джей Крепс, Неха Нарход и Джун Рао придумали Kafka: сервисы публикуют **события** в лог, другие сервисы читают из лога независимо. Никакой прямой связанности. Так родился modern event-driven подход.

**Event** - неизменяемый (immutable) факт того, что произошло. OrderCreated, PaymentProcessed, ItemShipped. Event описывается в прошедшем времени - он уже случился, его нельзя 'отклонить'. Компоненты подписываются на события и реагируют автономно, не зная друг о друге.

**Слабая связанность** - главное преимущество. Publisher не знает о subscribers. Новый сервис аналитики подключается к Kafka topic без единого изменения в OrderService. Падение NotificationService не влияет на InventoryService. Масштабирование каждого consumer независимо. Kafka в Netflix обрабатывает 700 млрд событий в день - именно благодаря этой развязке.

Свойство EventsОписание
ImmutableФакт нельзя изменить - OrderCreated уже произошёл
Past tenseИменование в прошедшем времени: Created, Updated, Deleted
Self-containedСодержит всю необходимую информацию для обработки
OrderedСобытия одного агрегата строго упорядочены по времени
DurableХранятся в брокере для replay и fault tolerance

Почему events описываются в прошедшем времени (OrderCreated, а не CreateOrder)?

Commands - запросы на действие

**Command** - запрос на выполнение действия. CreateOrder, CancelSubscription, UpdateProfile. В отличие от event, command **может быть отклонён**: недостаточно средств, товара нет на складе, нет прав. Command адресован конкретному handler, а не broadcast всем. Это принципиальная семантика: command - намерение, event - результат.

**Event vs Command:** Event - то, что произошло (факт, immutable, broadcast). Command - то, что хотим сделать (запрос, может быть отклонён, адресован конкретному handler). Поток: Command -> обработка и валидация -> Event (если успешно).

СвойствоCommandEvent
ИменованиеИмператив: CreateOrderПрошедшее время: OrderCreated
АдресатОдин конкретный handlerВсе подписчики (broadcast)
Может быть отклонён?Да - валидация и бизнес-правилаНет - это уже произошедший факт
СодержитНамерение + параметрыФакт + полные данные события
Idempotent?Должен бытьПо природе (факт однократен)

В хорошо спроектированной системе command handlers - тонкий слой валидации и координации. Вся реактивная логика - уведомления, аналитика, синхронизация - живёт в event handlers. Новый подписчик не требует изменения command handler. Именно это даёт EDA эластичность: Uber добавляет новый сервис аналитики поездок - и нулевых изменений в TripService.

Пользователь отправляет CancelOrder. Заказ уже отправлен и не может быть отменён. Что произойдёт?

Event Sourcing

Традиционная БД хранит **текущее состояние**: баланс = 1500. **Event Sourcing** хранит **все события**: Deposited(1000), Deposited(800), Withdrawn(300). Текущее состояние = replay всех событий. Audit log бесплатно - это и есть хранилище. Greg Young представил этот паттерн в 2010 году, вдохновившись... бухгалтерскими книгами. Двойная запись в бухгалтерии - тот же принцип с XIV века.

**Преимущества Event Sourcing:** 1. Полная история - восстановить состояние на любой момент. 2. Audit trail бесплатно. 3. Легко добавлять новые projections без изменения данных. 4. Debugging: воспроизвести баг, replay-ив события. 5. Temporal queries: 'какой был баланс вчера в 14:23?'

Replay миллиардов событий может быть медленным. Решение - **snapshots**: периодически сохранять текущее состояние. При восстановлении: загрузить последний snapshot + replay событий после него. EventStoreDB делает это автоматически каждые N событий.

СвойствоTraditional DBEvent Sourcing
ХранитТекущее состояниеВсе события с начала времён
ИсторияПотеряна (UPDATE перезаписывает)Полная, immutable
Восстановление на датуНевозможно без WAL/backupreplay до нужного момента
Audit trailОтдельная реализацияВстроен - events и есть audit
СложностьПростой CRUDВыше: event store, projections, versioning
Объём данныхТолько актуальноеРастёт постоянно (retention policy)

Какую проблему НЕ решает event sourcing?

CQRS

**CQRS** (Command Query Responsibility Segregation) - Грег Янг и Удо Удо Фарке, 2010. Разделение моделей чтения и записи. Команды (записи) идут в event store. Запросы (чтения) идут в оптимизированные read-модели (projections). Одни и те же события строят разные projections - каждая в формате, удобном для конкретных запросов. Netflix строит сотни projections из одного потока Kafka событий.

**Eventual consistency в CQRS:** Read-модели обновляются асинхронно после публикации события. Между записью события и отражением в projection есть задержка - обычно миллисекунды, иногда секунды при высокой нагрузке. Для большинства use cases это приемлемо. Для критичных операций (баланс счёта, инвентарь) - нужен read-your-writes паттерн или версионирование.

СвойствоБез CQRSС CQRS
Модель данныхОдна для всегоОтдельные для write и read
МасштабированиеОдинаковое для read/writeНезависимое: 10 read replicas, 1 write
Оптимизация запросовКомпромиссная для всехRead-модель оптимальна под конкретный запрос
ConsistencyStrongEventual (задержка в миллисекундах)
СложностьНизкаяВысокая: projections, синхронизация, versioning

CQRS и Event Sourcing часто используются вместе, но это **разные паттерны**. CQRS применяется без ES: разделить read/write модели в обычной PostgreSQL. ES используется без CQRS: одна unified модель. Вместе они дают максимальную гибкость - и максимальную сложность. Axon Framework, EventStoreDB, Confluent Platform строят экосистемы именно вокруг этой комбинации.

Event sourcing подходит для любой системы

Event sourcing добавляет значительную сложность: event versioning (формат события изменился - старые данные?), eventual consistency, GDPR compliance, растущий объём данных. Для простого CRUD - overkill.

Event sourcing оправдан когда: история критична (финансы, медицина, аудит), нужны temporal queries, много разных view на одни данные, нужен replay для debugging или миграции. Для блога или TODO-листа - обычная PostgreSQL надёжнее и проще.

Пользователь создал заказ и сразу открывает список заказов. Заказа в списке нет. Почему?

Ключевые идеи

  • **Events** - immutable факты (OrderCreated): broadcast всем подписчикам, не могут быть отклонены
  • **Commands** - запросы на действие (CreateOrder): адресованы конкретному handler, могут быть отклонены
  • **Event Sourcing** - хранение всех событий вместо текущего состояния: полная история, audit trail, temporal queries, snapshots для производительности
  • **CQRS** - раздельные модели для write (event store) и read (projections): независимое масштабирование, eventual consistency
  • Комбинация ES + CQRS - проверенный паттерн для финансов и систем с аудитом; избыточна для простого CRUD
  • Главное противоречие ES: immutable events vs GDPR right to erasure - решается crypto-shredding

Связанные темы

EDA - архитектурный паттерн, реализуемый через конкретные технологии:

  • Message Brokers: Kafka vs RabbitMQ vs NATS — Брокеры - транспортный уровень для events и commands
  • Batch vs Stream Processing — Events - фундамент stream processing; batch обрабатывает события пакетами
  • Event-Driven паттерны — Saga, Outbox, DLQ - production паттерны поверх EDA

Вопросы для размышления

  • Git хранит историю как цепочку коммитов. Какие паттерны из этого урока есть в Git?
  • Как решить проблему GDPR (right to erasure) в системе с event sourcing?
  • В каких сценариях eventual consistency CQRS неприемлема? Как это компенсировать?

Связанные уроки

  • stream-01 — Batch vs stream - фундамент для понимания EDA
  • stream-03 — Kafka, RabbitMQ, NATS - транспорт для events и commands
  • bt-17-event-driven — Паттерны EDA: Saga, Outbox, DLQ на практике
  • bt-18-saga — Saga pattern для распределённых транзакций через events
  • ds-02-cap-theorem — Eventual consistency в CQRS - следствие CAP-теоремы
  • isd-10-message-queues
  • dist-07-transactions
Event-Driven Architecture

0

1

Войти