Real-Time Backend
Event-Driven архитектура
2011 год, LinkedIn: сайт падает под нагрузкой. Каждый сервис дёргает каждый напрямую - сотни HTTP-вызовов в цепочке. Решение - Kafka и переход на event-driven. Сегодня 700+ микросервисов LinkedIn общаются через события, обрабатывая 7 трлн сообщений в день.
- Netflix хранит историю просмотров как журнал событий (Event Sourcing) - это позволяет рекомендательной системе реплеить историю и строить новые модели без миграций данных
- Uber ETA-сервис подписан на события `driver.location.updated` от тысяч водителей и пересчитывает время прибытия реактивно - без polling и без прямых вызовов между сервисами
- LinkedIn использует Kafka как центральный event bus: сервис вакансий публикует `JobPosted`, а рекомендации, аналитика и email-нотификации - независимые consumer-ы одного topic-а
- Axon Framework реализует CQRS+ES в Java: команда `PlaceOrderCommand` превращается в `OrderPlacedEvent`, который хранится в EventStore и проецируется в несколько read model-ей
Event Driven
В Event-Driven Architecture (EDA) компоненты системы не вызывают друг друга напрямую - они публикуют **события** (events) и реагируют на события других. Событие - это неизменяемый факт: «заказ создан», «платёж прошёл», «водитель сдвинулся на 200 м». Никто не ждёт ответа - сервис опубликовал и двинулся дальше.
Uber строит ETA-сервис именно так: тысячи водителей ежесекундно генерируют события `driver.location.updated`, а сервис расчёта времени прибытия подписан на этот поток и пересчитывает маршруты реактивно, без polling и без прямых вызовов к сервису геолокации.
- **Слабая связанность** - producer не знает, кто слушает и сколько их
- **Масштабируемость** - consumer-ы добавляются без изменения producer-а
- **Resilience** - сбой одного consumer не блокирует остальных и не роняет producer
- **Audit trail** - поток событий сам по себе журнал изменений
Uber ETA-сервис потребляет события `driver.location.updated`. Что произойдёт, если ETA-сервис упадёт на 30 секунд?
Event Sourcing
Event Sourcing - паттерн хранения состояния: вместо snapshot-а текущего состояния в БД хранится **журнал всех событий**, которые к нему привели. Текущее состояние - это проекция (fold) поверх журнала. Netflix применяет ES для истории просмотров: каждое `VideoPlayed`, `VideoPaused`, `VideoCompleted` пишется в append-only лог. Состояние профиля - результат применения всех этих событий.
**Axon Framework** (Java) - production-ready реализация CQRS+ES. Каждый aggregate хранится как журнал доменных событий. Команда `PlaceOrderCommand` превращается в событие `OrderPlacedEvent`, которое записывается в `EventStore` и проецируется в read model. Команда из LinkedIn - используют ES для аудита изменений профилей пользователей.
- **Полная история** - можно ответить «что было с заказом в 14:32 вчера»
- **Debugging** - воспроизвести любой баг, реплеив события до момента сбоя
- **Temporal queries** - состояние объекта на любой момент времени
- **Компромисс** - eventual consistency между write и read model, сложность проекций
В системе с Event Sourcing разработчик хочет узнать остаток на счёте пользователя на 1 января 2024. Как это сделать?
Event Bus
Event Bus - инфраструктурный компонент, который принимает события от producer-ов и доставляет их subscriber-ам. Это центральная шина коммуникации в EDA. LinkedIn перевёл межсервисную коммуникацию на Kafka в 2011 году - Kafka и стал их event bus для 700+ микросервисов. Каждый сервис публикует события в topic-и, другие - читают.
- **In-process bus** - библиотека внутри одного процесса (NestJS EventEmitter, Spring ApplicationEventPublisher). Не переживает рестарт.
- **Message broker** - внешний сервис (Kafka, RabbitMQ, Redis Streams). Durable, cross-service, масштабируемый.
- **Cloud event bus** - AWS EventBridge, GCP Pub/Sub. Managed, serverless-friendly.
**Consumer Group** - ключевой паттерн Kafka. Несколько instance одного сервиса объединяются в group и делят партиции между собой (параллельная обработка). Разные сервисы - разные groups - каждый получает все события независимо.
LinkedIn запускает новый сервис `RecommendationService`, который должен реагировать на события `ProfileUpdated`. Что нужно сделать в уже работающей Kafka-системе?
Event Replay
Event Replay - повторное воспроизведение исторических событий. Если журнал событий хранится достаточно долго (Kafka по умолчанию - 7 дней, Event Store - бессрочно), новый сервис может «прокрутить» всю историю с самого начала и построить актуальную проекцию. Netflix при запуске нового аналитического сервиса реплеит историю просмотров - миллиарды событий - и получает готовую модель без миграций.
- Новый сервис стартует с offset=0 (начало topic-а / event store)
- Читает события батчами, строит in-memory или persistent проекцию
- После catch-up переходит в real-time режим, читая новые события
- Старый сервис продолжает работать - zero downtime migration
**Pitfall:** при replay нельзя повторно запускать side effects (отправку email, списание денег). Проекция должна быть **idempotent** - применять событие дважды даёт тот же результат. Паттерн: хранить `event_id` и пропускать уже обработанные.
Event Replay и Event Sourcing - одно и то же
Event Sourcing - паттерн хранения состояния через журнал событий. Event Replay - операция воспроизведения этого журнала для построения новой проекции или отладки.
ES без replay возможен (просто хранение истории). Replay без ES - тоже: Kafka topic является журналом независимо от того, применяется ли ES в сервисах. Replay - инструмент, ES - архитектурный паттерн.
При replay событий `OrderPlaced` новый сервис отправляет приветственное письмо на каждое событие. Что произойдёт и как это исправить?
Ключевые идеи
- EDA: компоненты общаются через события, а не прямые вызовы - слабая связанность, независимое масштабирование
- Event Sourcing: состояние = fold по журналу событий; полная история, temporal queries, debug через replay
- Event Bus: инфраструктура доставки (Kafka, RabbitMQ, EventBridge) - producer не знает о consumer-ах, consumer groups обеспечивают параллелизм
- Event Replay: воспроизведение журнала для новых проекций или отладки; side effects должны быть idempotent
Связанные темы
EDA пересекается с несколькими архитектурными паттернами realtime-систем:
- CQRS — Command Query Responsibility Segregation - естественная пара Event Sourcing: команды пишут события, queries читают проекции
- Kafka Streams — Stream processing поверх event bus: stateful операции (join, aggregate, window) над потоками событий
- Microservices — EDA - предпочтительный способ коммуникации между микросервисами вместо синхронных REST-вызовов
- Saga Pattern — Распределённые транзакции через цепочку событий и компенсирующих команд - строится поверх event bus
Вопросы для размышления
- Какой сервис в проекте выиграл бы от перехода на event-driven взаимодействие - где сейчас tight coupling создаёт проблемы?
- Если завтра нужно добавить аналитику активности пользователей - как это проще сделать в EDA-системе vs синхронной?
- Какие данные в приложении имеет смысл хранить как журнал событий (Event Sourcing), а не как текущее состояние?