Event-Driven Architecture: когда события управляют системой
Традиционная архитектура: сервис A вызывает сервис B, ждёт ответ, продолжает. Event-Driven Architecture: сервис A публикует событие «заказ создан» — и забывает. Сервисы B, C, D подписаны и реагируют независимо. Никаких ожиданий, никаких цепочек вызовов.
Звучит красиво. На практике: либо решает проблемы масштаба, либо создаёт новые. Разберём когда и зачем.
Как это работает
В классическом request-response всё синхронно:
Клиент → API → Сервис заказов → Сервис оплаты → Сервис доставки → Email-сервис
Каждый шаг ждёт предыдущий. Если сервис доставки упал — клиент видит ошибку, хотя заказ создан и оплачен.
В Event-Driven — асинхронно:
Клиент → API → Сервис заказов → [Событие: OrderCreated]
↓ ↓ ↓
Оплата Доставка Email
Сервис заказов создал заказ, опубликовал событие — и всё. Дальше — не его проблема. Оплата, доставка, уведомления срабатывают независимо. Если доставка упала — оплата всё равно прошла, email ушёл.
Ключевые компоненты
| Компонент | Роль | Примеры |
|---|---|---|
| Event | Факт, который произошёл: «заказ создан», «платёж получен» | JSON-сообщение с типом и данными |
| Producer | Публикует события | Сервис заказов |
| Consumer | Подписывается и реагирует | Сервис оплаты |
| Broker | Принимает, хранит, доставляет события | Kafka, RabbitMQ, AWS SQS/SNS, NATS |
Когда EDA оправдана
1. Разные команды владеют разными сервисами
Если заказы, оплата и доставка — разные команды, синхронные вызовы создают жёсткую связанность. Команда заказов должна знать API команды оплаты. Изменение API оплаты → изменение в сервисе заказов. С событиями — команда заказов публикует OrderCreated, команда оплаты сама решает что с этим делать. Связанность минимальна.
2. Высокая нагрузка, нужно масштабировать независимо
В Чёрную пятницу сервис заказов загружен в 10 раз сильнее обычного. В синхронной модели сервис оплаты тоже должен выдержать пик, потому что он вызывается синхронно. В event-driven — события накапливаются в брокере, consumer'ы обрабатывают их в своём темпе.
3. Сложные цепочки действий
Заказ создан → проверить fraud → зарезервировать товар → списать баллы лояльности → отправить уведомление → через час проверить оплату → если не оплачен — отменить. Синхронно: адская цепочка вызовов с таймаутами и retry. Event-driven: каждое событие запускает следующий шаг — чистая, независимая логика.
4. Аудит и аналитика
События — это естественный audit log. Каждое изменение состояния системы зафиксировано как событие. Можно в любой момент восстановить историю, пересчитать аналитику, отладить баг.
Когда EDA НЕ нужна
1. Простой CRUD
Если ваша система — это «создать, прочитать, обновить, удалить» без сложных цепочек, EDA добавит сложности без пользы. Брокер, топики, consumer groups, dead letter queues — ради чего?
2. Сильная консистентность
В EDA консистентность — eventual (когда-нибудь согласуется). Если вам нужно «заказ и оплата либо вместе, либо никак» в реальном времени — EDA не подходит без паттернов вроде Saga. А Saga — это уже распределённые транзакции со своей сложностью.
3. Одна команда, один сервис
Если вся система — один сервис, который делает всё, EDA — оверкилл. Начните с синхронного подхода. Выделите события, когда появятся независимые сервисы.
Event Sourcing и CQRS
Два паттерна, которые часто идут с EDA:
Event Sourcing: вместо хранения текущего состояния («баланс: 1000») — храним все события («+500», «-200», «+700»). Текущее состояние = сумма событий. Плюсы: полный аудит, возможность пересчитать состояние на любой момент. Минусы: объём данных, сложность запросов «покажи текущий баланс».
CQRS (Command Query Responsibility Segregation): разделяем чтение и запись. Команды (createOrder, cancelOrder) меняют состояние через события. Запросы (getOrderHistory) читают из оптимизированной проекции. Плюсы: можно оптимизировать чтение отдельно от записи. Минусы: две модели данных, eventual consistency.
Выбор брокера
| Брокер | Модель | Когда |
|---|---|---|
| Kafka | Log-based. События хранятся и перечитываются. Высокая пропускная способность | Большие объёмы, event sourcing, несколько consumer'ов с разной скоростью |
| RabbitMQ | Queue-based. Сообщение доставлено → удалено | Классические очереди задач, меньше объём |
| AWS SNS/SQS | Управляемый сервис, queue-based | Нет желания админить брокер |
| NATS | Лёгкий, быстрый, pub/sub | Микросервисы, где важна скорость, а не хранение |
Правило: не начинайте с Kafka если у вас меньше 10 000 событий в секунду. RabbitMQ или NATS проще в эксплуатации.
Типичные ошибки
1. События без схемы
«Я буду слать JSON, а consumer'ы пусть разбираются». Через месяц — 15 версий одного события, половина consumer'ов ломается, никто не знает какие поля обязательны. Используйте схему (Avro, Protobuf, JSON Schema) с первого дня.
2. События как команды
Событие = факт, который уже произошёл. Не «пожалуйста оплати заказ», а «заказ создан». Первое — команда (императив). Второе — событие (факт). Команды создают связанность, события её убирают.
3. Один топик на всё
«Все события в один топик». Consumer'ы получают всё подряд и фильтруют — ад. Отдельные топики по доменам: orders, payments, shipments.
Итог
Event-Driven Architecture — мощный инструмент для систем с множеством независимых сервисов, сложными цепочками действий и потребностью в аудите. Но она добавляет существенную сложность: eventual consistency, распределённую отладку, мониторинг, дебаггинг.
Начинайте с синхронного подхода. Переходите к событиям когда: появились независимые команды/сервисы, синхронные цепочки стали узким горлом, нужен аудит.
Связанные статьи на сайте
- C4 Model: документируем архитектуру за 4 шага
- Модульный монолит или микросервисы: спектр решений
- ADR: как вести архитектурные решения
- Микросервисы vs монолит: когда что выбрать
- Справочник архитектурных паттернов ИТ-систем
Связанные статьи: Микросервисы vs монолит: когда что выбирать, Архитектурные паттерны ИТ-систем, 97 принципов архитектора ПО
- System Design: как проектировать масштабируемые системы — фреймворк проектирования и базовые компоненты (Kafka, репликация)
- DevOps: принципы и внедрение — CI/CD, мониторинг, observability