Событийная архитектура — это способ построить систему так, чтобы часть, где что-то произошло, сообщала об этом фактом, а не командовала остальными. Заказ оплачен — одна часть выпускает событие, склад, уведомления и отчёты каждый по-своему на него реагируют, и отправитель не знает, кто читает. Нужна она, когда сбой или медленная работа одной части не должны останавливать другие, или когда одно изменение интересно многим. Для простого приложения с одной базой она, скорее всего, лишняя.
Ниже условный пример, который показывает разницу, затем цена подхода, шесть вопросов для самопроверки и порядок, в котором стоит двигаться. Как строить такие системы на заказ, описано на странице «Event-driven система», здесь только объяснение и критерии выбора.
Одна и та же задача двумя способами
Возьмём условный интернет-магазин оптовых поставок. После оплаты заказа надо четыре вещи: отправить клиенту письмо, зарезервировать товар, выписать документы и записать заказ в учётную систему. Пример условный, но схема типична.
Способ первый: цепочка вызовов
Модуль заказов по очереди вызывает остальные: сначала склад, потом документы, потом учётную систему, потом почту. Каждый шаг ждёт предыдущего. Если учётная система на обслуживании, оплата «зависает» на третьем шаге: клиент заплатил, а заказ не оформлен, и непонятно, что с резервом. Чтобы добавить пятый шаг, например передачу в аналитику, нужно изменить модуль заказов, пересобрать его и проверить все остальные.
Способ второй: события
Модуль заказов сохраняет оплату и выпускает событие «заказ оплачен». Дальше его читают четыре независимых обработчика, каждый в своём темпе. Недоступна учётная система: письмо и резерв отработали, запись в учёт подождёт и выполнится, когда система вернётся. Аналитика подписывается на то же событие, и модуль заказов не меняется.
Но платите вы за это другим. Теперь «заказ оплачен» не означает «заказ полностью оформлен», и надо решить, что видит клиент в промежутке. Обработчик может получить одно и то же событие дважды. Чтобы понять, на каком шаге завис заказ, нужна сквозная диагностика, а не просмотр одного журнала.
| Вопрос | Цепочка вызовов | События |
|---|---|---|
| Что будет, если один шаг недоступен | Весь сценарий останавливается или откатывается | Остальные шаги выполняются, отстающий догоняет позже |
| Как добавить нового участника | Изменить и пересобрать вызывающий модуль | Подписаться на событие, источник не меняется |
| Когда известен итог | Сразу, ответ приходит вместе с вызовом | Позже: итог нужно показать пользователю как «в обработке» |
| Где искать причину сбоя | В одном журнале по порядку вызовов | По идентификатору цепочки в журналах нескольких частей |
| Что со скоростью ответа | Пользователь ждёт все шаги | Пользователь ждёт только запись, остальное в фоне |
Что на самом деле называют событием
В разговорах «событием» называют три разные вещи, и из-за этого проекты расходятся с ожиданиями. Первая — уведомление: «заказ изменился, идите посмотрите». Оно короткое, читатель сам запрашивает подробности. Вторая — событие с данными: «заказ оплачен, сумма, состав, клиент». Читателю ничего запрашивать не нужно, но состав события становится общим договором. Третья — журнал как источник истины, когда состояние системы собирается из ряда событий; это отдельная и более тяжёлая тема, разобранная в статье про CQRS и Event Sourcing.
Когда подрядчик говорит «сделаем событийную архитектуру», спросите, какое из трёх он имеет в виду. Для большинства бизнес-систем нужно первое или второе, третье встречается заметно реже.
Отдельно стоит отличать событие от команды. «Оплата получена» — событие: факт, который не оспорить. «Выпиши счёт» — команда: просьба, которую могут отклонить, и тогда отправитель обязан это заметить. Чаще всего встречается команда, замаскированная под событие: отправитель ждёт, что кто-то обязательно отреагирует, но ни с кем об этом не договорился. Через месяц выясняется, что обработчика нет, а счёт никто не выписывает.
Что вы получаете
- Изоляция сбоев. Отказ второстепенной части не останавливает основной сценарий. Приём заказа не зависит от того, доступен ли в эту минуту почтовый сервис.
- Расширяемость. Новый потребитель данных подключается подпиской. Источник не нужно трогать, а значит, не нужно перепроверять всё остальное.
- Выравнивание нагрузки. Пиковый поток накапливается в очереди и обрабатывается в удобном темпе. Тяжёлые операции, например формирование документов, не мешают приёму.
- Раздельные скорости развития. Команды меняют свои обработчики независимо, если контракт события остаётся прежним.
- След процесса. Если события сохраняются, по ним видно, что и когда происходило. Это не журнал аудита, но полезный материал для разбора инцидентов.
Что вы платите
У каждого пункта выше есть оборотная сторона. Её полезно знать до начала, а не после первого инцидента.
| Цена | Как проявляется | Чем закрывается |
|---|---|---|
| Итоговая согласованность | Данные в разных частях расходятся на секунды или минуты | Промежуточные состояния в интерфейсе, сверка, понятные сообщения пользователю |
| Дубли | Обработчик получает событие дважды и делает дело дважды | Идемпотентность: повтор даёт тот же результат, идентификаторы обработанных событий |
| Нарушение порядка | Событие «отменён» обрабатывается раньше «создан» | Ключ порядка, версия объекта в данных, один обработчик на ключ |
| Потерянные сообщения | Запись в базу прошла, а событие не отправилось | Запись события в той же транзакции, что и данные, и отдельный процесс отправки |
| Сложная отладка | Причина сбоя разбросана по нескольким частям | Идентификатор цепочки, сквозная трассировка, панель очередей |
| Новая эксплуатация | Нужно следить за очередями и недоставленными сообщениями | Оповещения по возрасту старейшего сообщения, регламент разбора, назначенный человек |
Самая частая ошибка — недооценить последнюю строку. Очередь без человека, который смотрит на неё каждый день, превращается в место, где заказы лежат неделями. Как это закрывается технически, описано на странице «Event-driven система».
Шесть вопросов, чтобы понять, нужна ли она вам
Ответьте на вопросы. Три и более ответов «да» — повод изучить подход всерьёз. Меньше — скорее всего, хватит обычных вызовов и фоновых задач.
- Бывает ли, что сбой второстепенной системы (почта, учёт, платёжный провайдер) останавливает основной сценарий?
- Нужно ли одному изменению вызывать больше трёх последствий в разных частях системы?
- Подключаются ли к вашим данным новые потребители чаще раза в квартал?
- Есть ли пики нагрузки, при которых приём данных должен продолжаться, а обработка может отставать?
- Допустимо ли, чтобы результат появлялся у пользователя не мгновенно, а через несколько секунд?
- Есть ли в команде человек, который будет отвечать за очереди и разбирать недоставленное?
Шестой вопрос не техническая формальность. Если ответ на него «нет», остальные ответы почти не имеют значения: система без хозяина очередей начнёт терять данные тихо.
Когда событийная архитектура не нужна
- Одно приложение, одна база, одна команда. Обычные вызовы внутри процесса проще, быстрее и надёжнее. Фоновые задачи для отправки писем и формирования отчётов можно сделать очередью внутри приложения без всякой «событийной архитектуры».
- Нужен немедленный и точный ответ. Остаток на складе в момент оплаты, расчёт цены в корзине, проверка прав. Это запросы, и они остаются вызовами.
- Операция из нескольких изменений должна произойти целиком или не произойти вовсе. В пределах одной базы для этого есть транзакции. События превратят это в цепочку компенсаций, которые написать и проверить сложнее.
- Медленно один запрос. Очередь не ускорит плохой запрос, а спрячет его в фон. Сначала найдите причину замером, как в статье «Микросервисы или монолит».
- Нет людей и инструментов наблюдения. Без панели очередей и оповещений событийная схема хуже простой.
Как вводить: по одному потоку
Переписывать систему целиком на события не стоит. Рабочий порядок такой:
- Найти один поток с дорогой ценой сбоя. Например, приём заказов, которые теряются, когда недоступна учётная система. Не самый большой поток, а самый болезненный.
- Записать контракт события. Название в прошедшем времени, поля, версия, ключ порядка, кто выпускает и кто читает.
- Сначала надёжность отправителя, потом читатели. Событие записывается вместе с изменением данных и отправляется отдельным процессом. Иначе вы получите потери, которые не заметите.
- Сделать читателей устойчивыми к повторам. Проверить на стенде: повтор сообщения, обработчик упал посередине, события пришли не по порядку.
- Запустить новый путь рядом со старым. Сравнить результаты и только затем отключить старую связь.
- Включить наблюдение до запуска. Глубина очереди, возраст старейшего сообщения, число недоставленных.
- Только после этого брать следующий поток. Если первый не показал пользы, остановитесь.
Носитель событий выбирайте по нагрузке и составу команды: для небольших потоков хватает очереди в базе данных, которая у вас уже есть, для задач с маршрутизацией и повторами подходит брокер вроде RabbitMQ, журнал вроде Kafka нужен, когда поток большой и требуется повторный разбор истории. Сравнение по критериям собрано на странице услуги.
Типичные ошибки
Эти ошибки повторяются от проекта к проекту, и каждую из них видно на этапе проектирования.
| Ошибка | Чем кончается | Как избежать |
|---|---|---|
| События на каждое изменение поля | Поток шумит, читатели тонут в мелочах, схема меняется каждую неделю | События по смыслу бизнеса: «заказ оплачен», а не «поле 17 изменилось» |
| Скрытая команда под видом события | Никто не отвечает за выполнение, сбой не замечают | Если нужно, чтобы кто-то обязательно сделал, это команда с адресатом и контролем результата |
| Поле меняют без версии | Чужие обработчики ломаются без предупреждения | Каталог событий, версии, проверка совместимости в сборке |
| Ожидание мгновенного итога | Пользователь обновляет страницу и видит старое | Состояние «в обработке» в интерфейсе, результат из ответа на команду |
| Очередь без владельца | Недоставленные сообщения копятся, заказы теряются тихо | Назначенный человек, оповещение по возрасту сообщения, регламент разбора |
| Брокер для двух сообщений в день | Новая инфраструктура без пользы | Очередь в базе или обычная фоновая задача |
Что делаем мы и с чего начать
Начинаем с обследования: 200 000 ₽ и 2–3 недели за один-два процесса, 350 000 ₽ и 3–4 недели, если потоки проходят через несколько систем. На выходе карта потоков, список мест, где события оправданы и где хватит вызова, черновик каталога событий, выбор носителя с причинами и вилка бюджета. Обследование вычитается из первого этапа, если продолжаете с нами. Цены по ступеням собраны на странице «Тарифы».
Дальше, если нужно, пилот одного потока, затем перевод остальных, и на любой ступени можно остановиться. Мы делаем один контур, а не шину предприятия на все системы; не дежурим круглосуточно; лицензии ФСТЭК у нас нет; исходный код и каталог событий лежат в вашем репозитории с первого коммита. Подробности работы описаны на странице «Event-driven система».
Частые вопросы
Что такое событийная архитектура простыми словами?
Это построение системы, в которой часть, где что-то произошло, сообщает об этом фактом («заказ оплачен»), а остальные части сами решают, как отреагировать. Отправитель не знает получателей и не ждёт ответа.
Чем событие отличается от вызова API?
Вызов просит сделать и ждёт результата, адресат известен. Событие сообщает о произошедшем, адресаты не известны и результата не возвращают. Для вопросов, где нужен немедленный ответ, вызов остаётся лучшим выбором.
Нужен ли для событий отдельный брокер сообщений?
Не всегда. Для небольшого потока хватает очереди в той же базе данных. Отдельный брокер оправдан при заметной нагрузке, сложной маршрутизации и независимых читателях. Это решение принимается по замеру, а не по моде.
Как избежать дублей и потерянных сообщений?
Отправитель записывает событие в той же транзакции, что и изменение данных, а отдельный процесс передаёт его. Читатель выдерживает повтор: запоминает обработанные идентификаторы или записывает результат так, чтобы повтор ничего не менял. Необработанное попадает в отдельный список, который кто-то смотрит.
Подходит ли событийная архитектура небольшой системе?
Чаще всего нет. Если система состоит из одного приложения и одной базы, хватает вызовов и фоновых задач. Смысл появляется, когда участников несколько, внешние системы нестабильны или потребителей данных становится много.
Чем событийная архитектура отличается от микросервисов?
Это разные решения. Микросервисы делят систему на независимо выпускаемые части, события — один из способов общения между частями, в том числе и внутри монолита. Можно иметь события без микросервисов и наоборот. Как выбрать между монолитом и сервисами, описано в статье о микросервисах и монолите.
Сколько стоит перевод системы на события?
Зависит от числа потоков и систем. Обследование стоит 200 000 ₽ за один-два процесса и 350 000 ₽ при нескольких системах, пилот одного потока 150 000–400 000 ₽, тяжёлый пилот с боевой интеграцией 400 000–1 200 000 ₽. Вывод в эксплуатацию от 1 500 000 ₽ этапами по 300 000–700 000 ₽.
Можно ли остановиться после пилота?
Да. Сделанное остаётся у вас по акту вместе с исходным кодом и каталогом событий. Систему можно развивать своей командой или передать другому подрядчику.