Событийная архитектура: когда помогает, а когда создаёт шум
Практический разбор event-driven подхода - какие задачи он решает, где добавляет сложность без пользы и как понять, нужен ли он вам.
За последний год у меня несколько раз был один и тот же разговор: команда предлагает перейти на событийную архитектуру, предложение звучит убедительно, бизнес соглашается, не вполне понимая, что именно покупает. Иногда это заканчивается хорошо. Нередко - нет.
Сам паттерн надёжный. Проблема в том, что его применяют слишком широко - в ситуациях, где более простая модель работала бы лучше и дешевле в обслуживании.
Что на самом деле даёт этот паттерн
В событийной архитектуре компоненты не вызывают друг друга напрямую. Вместо этого один компонент публикует событие - «заказ оформлен», «платёж получен», «документ загружен» - а любой другой компонент, которому это событие важно, реагирует на него самостоятельно.
Связанность слабая. Производитель не знает, кто слушает. Потребитель не знает, кто произвёл. Новые потребители можно добавлять, не трогая существующий код.
Это реально полезно - в конкретных ситуациях.
Когда паттерн оправдывает свою цену
Паттерн имеет смысл, если:
- На одно бизнес-событие должны реагировать несколько независимых систем. Оформление заказа может одновременно запускать склад, биллинг, уведомления и аналитику. Последовательные вызовы каждого сервиса хрупки и медленны.
- Нагрузка неравномерная. Очередь между производителем и потребителем позволяет поглощать пики без потери запросов.
- Важны аудит и воспроизведение. Лог событий даёт полную историю того, что происходило и когда. Его можно воспроизвести, чтобы восстановить состояние или накормить данными нового потребителя.
- Команды независимы. Если команда биллинга и команда исполнения деплоят по разным расписаниям, жёсткая связанность порождает координационные издержки, которые множатся со временем.
Где сложность появляется без соразмерной пользы
Паттерн нередко предлагают в меньших контекстах, куда он не вписывается:
- Прямолинейное CRUD-приложение с одним-двумя потребителями не нуждается в брокере сообщений. Прямой вызов или триггер в базе данных проще понять и отладить.
- Если бизнес не допускает eventual consistency - например, оплата должна подтвердиться до любых дальнейших действий - асинхронные события требуют аккуратной компенсирующей логики, которая зачастую сложнее исходного синхронного потока.
- Небольшие команды часто недооценивают операционную стоимость: управление инфраструктурой брокера, мониторинг отставания потребителей, обработка «ядовитых» сообщений и трассировка запросов через асинхронные границы требуют инструментов и дисциплины.
Что проверить перед выбором паттерна
Перед тем как рекомендовать событийный подход, я задаю эти вопросы:
- Сколько независимых потребителей должны реагировать на одно событие сегодня? Через год?
- Допускает ли бизнес-процесс задержку между событием и реакцией, или нужна синхронность?
- Кто будет обслуживать брокер и как будут диагностироваться инциденты?
- Есть ли у команды опыт с идемпотентностью и семантикой «at-least-once»?
- Как выглядит откат, когда что-то пошло не так в середине потока?
Если ответы - «один потребитель, нулевая терпимость к задержке и никто ни разу не запускал Kafka» - синхронный вариант почти наверняка правильная отправная точка.
Практическая отправная точка
Если вы оцениваете этот паттерн, начните с бизнес-процесса, а не с технологии. Опишите события, которые уже существуют в домене - что происходит, кто должен реагировать и какова терпимость к задержке. Потом проверьте, способна ли команда обслуживать то, что вы предлагаете.
Архитектура должна со временем снижать операционное трение, а не немедленно увеличивать его с обещанием будущей гибкости.