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

Событийная архитектура позволяет сервису сообщить об изменении, не дожидаясь, пока все потребители его обработают. Это уменьшает связанность компонентов, но добавляет новую цену: задержки, повторы, согласование данных и отдельную инфраструктуру. Ниже — разбор архитектурных последствий без привязки к конкретному брокеру или версии библиотеки: универсальной версии для этого подхода нет.
Что меняется, когда вызов становится событием
В синхронной схеме сервис A вызывает B и получает ответ в рамках одного запроса. Если B недоступен или отвечает медленно, это напрямую влияет на пользователя A. В событийной схеме A записывает факт, например «заказ создан», а один или несколько потребителей забирают его позже. A может завершить собственную работу раньше, но уже не знает, когда и успешно ли закончилась вся цепочка.
Это не просто замена HTTP на очередь. Событие описывает то, что уже произошло; команда просит выполнить действие. «Оплата принята» — событие, «списать деньги» — команда. Если смешать их, потребители начинают трактовать факт как приказ, а повторная доставка может привести к повторному действию.
Событийность переносит ожидание из пользовательского запроса в систему обработки. Она не убирает ожидание — она меняет, кто за него отвечает и как вы замечаете сбой.
Термин. Идемпотентность — свойство обработки, при котором повтор одного и того же события не меняет результат второй раз. Она особенно важна, потому что доставка «как минимум один раз» может означать повторы после тайм-аута или сбоя.
За что платите: сбои становятся менее локальными
При прямом вызове цепочка видна в одном запросе: есть ответ или ошибка. С очередью успешная отправка ещё не означает, что потребитель выполнил работу. Сообщение может задержаться, попасть в повторную обработку или застрять из-за события, которое потребитель не умеет разобрать. Ошибка перестаёт выглядеть как единичный сбой запроса и становится расхождением состояния между сервисами.
Доставка не равна однократному выполнению
Большинство практических систем проектируют с расчётом на повторы: подтверждение может потеряться, потребитель — упасть после записи в базу, но до подтверждения брокеру. При повторной доставке операция запускается снова. Обещание «ровно один раз» в отдельных инструментах обычно ограничено конкретным участком обработки и не гарантирует однократный эффект во всех внешних системах.
Защита — не магическая настройка брокера, а дизайн обработчика. Храните идентификатор события или бизнес-операции, проверяйте, обработан ли он, и делайте запись результата вместе с отметкой об обработке в согласованной транзакции там, где это возможно. Если обработчик вызывает сторонний API, проверьте, поддерживает ли тот ключ идемпотентности: локальная база не может отменить уже выполненный внешний платёж.
Порядок событий не всегда совпадает с порядком изменений
Два события одного заказа могут прийти разным потребителям с задержкой или повтором. Если потребитель ожидает строгую последовательность, нужно определить, в каких границах она гарантируется, и что делать с пропуском или устаревшей версией. Глобальный порядок для всех событий дорого обходится и часто не нужен; обычно его ограничивают сущностью или ключом, например конкретным заказом.
Схема сообщения тоже становится контрактом. Переименование поля может сломать старого потребителя, который ещё не обновился. Поэтому изменения проектируют с обратной совместимостью: сначала добавляют поле, которое старые читатели игнорируют, затем обновляют потребителей, а удаление старого поля откладывают до подтверждения, что оно больше не используется.
Сравнение: синхронный вызов, очередь и журнал событий
| Подход | Что выигрываете | Цена и ограничение | Когда уместен |
|---|---|---|---|
| Синхронный запрос | Немедленный ответ и понятная граница ошибки | Зависимость от доступности и задержки вызываемого сервиса | Пользователю нужен результат прямо сейчас |
| Очередь команд или задач | Буфер между отправителем и обработчиком, возможность повторить работу | Задержка, повторы и необходимость отслеживать невыполненные задачи | Работу можно завершить позже, например сформировать отчёт |
| Журнал бизнес-событий | Несколько независимых потребителей могут реагировать на один факт; события можно хранить и переигрывать по правилам системы | Версионирование контрактов, порядок, хранение и сложная диагностика | Изменение должно запускать независимые реакции в нескольких системах |
Очередь задач и журнал событий не тождественны: первая обычно отвечает на вопрос «что выполнить», второй — «что произошло». Конкретные гарантии доставки и хранения зависят от выбранного брокера и его конфигурации; их нужно сверять с документацией продукта, а не переносить с одной платформы на другую.
Сколько стоит эксплуатация
Вместо выдуманного универсального процента накладных расходов измеряйте их на своём потоке. Важна не только средняя задержка, но и хвост: редкие долгие ожидания могут удерживать устаревшее состояние у пользователя. Нужны как минимум следующие показатели:
- Возраст самого старого необработанного сообщения. Если он растёт, потребитель не успевает или регулярно падает.
- Размер очереди и скорость её изменения. Сам по себе объём без динамики не показывает, справляется ли система.
- Время от публикации до завершения обработки. Сравнивайте медиану и высокие перцентили, а не только среднее.
- Число повторов и сообщений, отправленных в карантин или отдельную очередь ошибок. Рост часто указывает на несовместимость схемы или устойчивую ошибку обработчика.
- Расхождение бизнес-состояний. Например, сколько заказов отмечены оплаченными, но не переданы в доставку, и как долго сохраняется такое состояние.
Эти метрики требуют работы: нужно связать идентификатор события с бизнес-операцией, настроить оповещения и определить ответственного за повторную обработку. Логи без сквозной идентификации превращают расследование в поиск сообщения среди независимых сервисов.
Где схема ломается первой
Запись в базу прошла, публикация — нет
Сервис сохраняет заказ, затем публикует событие. Если процесс падает между действиями, заказ есть, а события нет. Обратный порядок создаёт противоположный риск: событие опубликовано, а запись не сохранилась. Типичное решение — transactional outbox: событие записывается в одной транзакции с бизнес-данными, а отдельный процесс публикует его. Это уменьшает окно потери, но не отменяет повторную публикацию и необходимость идемпотентного потребителя.
Повторное событие повторяет эффект
Потребитель увеличивает баланс, отправляет письмо или создаёт задачу. Повтор может повторить и побочный эффект. Для каждого обработчика определите ключ дедупликации и срок хранения отметок: если удалить их раньше возможного периода повторной доставки, старое событие снова будет выглядеть новым.
Очередь растёт, хотя сервисы «зелёные»
Проверка доступности отвечает, что процесс запущен, но не показывает, успевает ли он обработать поток. Всплеск нагрузки, медленный внешний API или постоянная ошибка одного типа сообщений могут накапливать отставание. Ограничивайте параллелизм с учётом ресурсов и внешних лимитов, а для повторов используйте задержку: непрерывные мгновенные повторы способны усилить перегрузку.
Невозможно понять, что уже сделано
Статусы «в очереди» и «успешно» слишком грубы для длинной цепочки. Зафиксируйте бизнес-состояния и допустимые переходы: например, создан, ожидает оплаты, оплачен, передан в доставку. Определите, кто и как разбирает застрявший процесс, можно ли безопасно повторить шаг и когда требуется ручная компенсация.
Когда выбирать событийную архитектуру
События оправданы, если несколько независимых потребителей реагируют на один факт, часть работы можно выполнить позже, а временная недоступность одного компонента не должна блокировать остальные. Цена — дополнительные процессы, контракты и наблюдаемость. Если операция должна вернуть окончательный результат немедленно, а за ней стоит один сервис, прямой запрос часто проще и честнее.
Перед внедрением ответьте на три вопроса: какой именно сбой хотите изолировать; какую задержку пользователь готов принять; кто будет разбирать очередь ошибок и восстанавливать бизнес-состояние. Если на третий вопрос нет владельца, вы не устранили операционную работу, а отложили её.
Что делать дальше
Начните с одного конкретного процесса, а не с перевода всей системы на события. Опишите событие как неизменяемый факт, зафиксируйте поведение при повторе и сбое, затем проведите тест: остановите потребителя после сохранения результата, но до подтверждения доставки. После запуска следите за возрастом очереди, задержкой обработки и долей повторов. Инвалидация кеша и построение распределённых транзакций — отдельные темы; здесь важны надёжная доставка и цена асинхронности.
Разрушитель авто-разводов: Фото заказ-наряда СТО: завышения, дубли работ, лишние позиции.
Спросить за 15 ₽Источники: AWS Prescriptive Guidance: Event-driven architecture, Apache Kafka Documentation
Частые вопросы
Событие может прийти дважды?
Событийная архитектура гарантирует, что ничего не потеряется?
Нужен ли брокер, если у меня два сервиса?
Что делать, если потребитель не может обработать событие?
Можно ли обещать обработку ровно один раз?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.