Кладовочка

О разном, чтобы было под рукой онлайн

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, распределённую отладку, мониторинг, дебаггинг.

Начинайте с синхронного подхода. Переходите к событиям когда: появились независимые команды/сервисы, синхронные цепочки стали узким горлом, нужен аудит.

Связанные статьи на сайте


Связанные статьи: Микросервисы vs монолит: когда что выбирать, Архитектурные паттерны ИТ-систем, 97 принципов архитектора ПО