Event-driven система: разработка событийной архитектуры под вашу задачу

Event-driven система — это система, в которой части общаются событиями: «заказ принят», «оплата получена», «остаток изменился». Отправитель не ждёт получателя, а получателей можно добавлять, не трогая отправителя. Такой подход нужен не всякой системе: он избавляет от одних проблем и приносит другие. Мы разбираем ваши потоки, оставляем события там, где они окупаются, и проектируем контракты, повторы без дублей и видимость сбоев так, чтобы систему могла сопровождать ваша команда.

Обследование потоков — от 200 000 ₽, 2–3 неделиНачинаем с одного потока, а не с перестройки всей системыИсходный код и описание событий в вашем репозитории с первого коммита

С чем приходят

Когда события нужны, а когда хватит обычных вызовов

Один сценарий тянет за собой десять действий

После принятия заказа надо уведомить клиента, зарезервировать товар, выписать документ, обновить отчёт и сообщить в 1С. Сейчас это одна цепочка, и сбой в хвосте роняет начало.

Внешняя система то доступна, то нет

Приём заявок не должен зависеть от того, жива ли сегодня учётная система или платёжный провайдер. Заявку надо принять и довести позже.

Новому потребителю приходится править отправителя

Аналитике понадобились те же данные, и команда заказов снова вносит изменения в свой код.

События уже есть, но ими никто не управляет

Очередь поставили, а что в ней лежит и что делать с зависшим сообщением, не записано. Дубли находят по жалобам.

Основы

Событие, команда, запрос: что чем является и почему это важно

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

Что передаём
Пример
Кто знает получателя
Что бывает при ошибке
Что передаёмСобытиефакт, который уже произошёл
Пример«Заказ оплачен», «Остаток изменился»; название в прошедшем времени
Кто знает получателяОтправитель не знает, кто его читает, и не ждёт ответа
Что бывает при ошибкеОбработчик упал: событие остаётся в очереди и обрабатывается повторно. Само событие отозвать нельзя, можно выпустить следующее, исправляющее
Что передаёмКомандапросьба сделать что-то
Пример«Выписать счёт», «Отправить письмо»; название в повелительной форме
Кто знает получателяОтправитель знает адресата и ждёт результата
Что бывает при ошибкеАдресат отказал: отправитель обязан это увидеть и решить, что делать дальше
Что передаёмЗапросвопрос о текущем состоянии
Пример«Какой остаток на складе»; ответ нужен сейчас
Кто знает получателяОтправитель знает адресата и блокируется до ответа
Что бывает при ошибкеНет ответа: пользователь ждёт. Здесь обычный синхронный вызов с тайм-аутом проще любой очереди
Контракт

Как выглядит событие, которое можно сопровождать

Событие — это публичный интерфейс системы, такой же, как API. Его меняют осторожнее, чем внутренний код, потому что читателей вы можете не знать.

Что записываем в каждое событие

  • Название и версия схемы. «Заказ оплачен», версия 2. Читатель по версии понимает, какие поля ждать.
  • Собственный идентификатор события. По нему получатель отличает повтор от нового события.
  • Время возникновения. Не время отправки и не время получения: порядок восстанавливают по первому.
  • Ключ порядка. Обычно идентификатор объекта, например заказа. События одного заказа идут по порядку, события разных заказов независимы.
  • Идентификатор исходной операции. Он позволяет проследить цепочку от запроса пользователя до последнего обработчика.

Короткое событие или полное

Короткое событие несёт только идентификатор объекта: «заказ 42 изменился». Получатель идёт за подробностями к источнику. Это просто и всегда актуально, но источник должен выдерживать запросы всех читателей и быть доступным. Полное событие несёт нужные поля: сумму, состав, статус. Читателю не нужно ходить к источнику, зато схема становится общим договором, и поле, которое вы решили переименовать, ломает чужие обработчики.

Мы выбираем по тому, кто читает: для внутренних потребителей годится полное событие, для внешних систем часто безопаснее короткое. Выбор записывается в каталог вместе с причиной.

Каталог событий

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

Взаимодействие

Хореография или оркестрация: кто управляет цепочкой

Когда шагов несколько, надо решить, кто знает весь порядок. От этого зависит, как вы будете искать причину сбоя.

Подход
Когда подходит
Что получаете
Что платите
ПодходХореографиякаждый сам реагирует на события
Когда подходитДва-четыре шага, независимые участки, порядок между ними не критичен
Что получаетеСлабая связность: новый участник подключается без изменений в остальных
Что платитеОбщую цепочку нигде не видно целиком. Чтобы понять, почему заказ завис, надо собрать следы из нескольких мест
ПодходОркестрацияодин координатор ведёт процесс
Когда подходитМного шагов с условиями, откатами и сроками, например заказ с резервом, оплатой и доставкой
Что получаетеСостояние процесса видно в одном месте, шаги и откаты записаны явно
Что платитеКоординатор знает всех участников и становится точкой, которую надо сопровождать и тестировать особенно тщательно
Надёжность

Дубли, порядок и зависшие сообщения: что решает получатель

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

Доставка бывает «хотя бы один раз»

Практически все брокеры гарантируют, что сообщение дойдёт хотя бы один раз, но не ровно один. Сеть оборвалась после обработки, подтверждение не дошло, сообщение пришло снова. Поэтому обработчик обязан давать тот же результат при повторе. Способы такие: хранить идентификаторы обработанных событий; записывать результат так, чтобы повторная запись ничего не меняла (установить статус, а не прибавить единицу); поставить в базе ограничение на повтор пары «событие, обработчик».

Порядок гарантируется только внутри ключа

Если события одного заказа обрабатываются разными обработчиками параллельно, «отменён» может обработаться раньше «создан». Решения два: направлять события одного ключа одному обработчику или хранить в данных версию объекта и отбрасывать устаревшие события. Глобальный порядок всех событий почти никогда не нужен и дорого стоит, поэтому мы сначала выясняем, где порядок действительно важен.

Ядовитое сообщение

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

Задержка видна пользователю

После «Оплатить» экран не должен показывать старый статус, если обработчик отстал на минуту. В интерфейсе вводим состояние «обрабатывается» и понятное сообщение при сбое. Если от результата зависит следующий шаг пользователя, этот шаг лучше сделать синхронным вызовом.

Выбор носителя

Чем передавать события: очередь в базе, брокер или журнал событий

Выбор делаем по нагрузке, составу команды и тому, кто будет этим управлять после сдачи. Нагрузку считаем на вашем профиле, а не по общим цифрам.

Носитель
Когда подходит
Что получаете
Что платите
НосительОчередь в PostgreSQLбез отдельного сервиса
Когда подходитНебольшие потоки, команда уже эксплуатирует PostgreSQL, события привязаны к данным в той же базе
Что получаетеНичего нового в эксплуатации, запись события и изменения в одной транзакции, простая диагностика обычными запросами
Что платитеПотолок по нагрузке ниже, чем у брокера. Читатели нагружают основную базу, если их много
НосительБрокер очередейнапример RabbitMQ
Когда подходитЗадачи и события с маршрутизацией, подтверждениями, повторами и отдельными очередями для читателей
Что получаетеЗрелые инструменты для повторов и списка недоставленных, нагрузка вне основной базы
Что платитеОтдельный сервис: мониторинг, обновления, копии конфигурации. Прочитанное сообщение пропадает, повторно прочитать историю нельзя
НосительЖурнал событийнапример Kafka
Когда подходитБольшой поток, несколько независимых читателей, нужен повторный разбор истории и построение новых представлений данных
Что получаетеСобытия хранятся заданный срок, новый читатель начинает с любого места, потоки легко масштабируются по ключу
Что платитеСамый тяжёлый в эксплуатации: разделы, смещения, порядок только внутри раздела. Для небольшой команды это почти всегда избыточно
Границы

Где события вредят

Нужен немедленный ответ

Проверка остатка перед оплатой, расчёт цены в корзине, вход пользователя. Здесь пользователь ждёт, и синхронный вызов с тайм-аутом честнее очереди.

Одна транзакция на несколько объектов

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

Нет людей, которые будут смотреть очереди

Асинхронная система требует панели, оповещений и дежурного порядка разбора. Без них сбои копятся молча. Круглосуточного дежурства мы не обеспечиваем, это надо закладывать у вас.

Что делаем

Из чего состоит работа и сколько она стоит

Цены совпадают со страницей тарифов. Цена после первой ступени называется по результатам, остановиться можно после любой.

Обследование: потоки и границы

Карта потоков, замер нагрузки и частоты сбоев, список мест, где события оправданы, и мест, где достаточно вызова. На выходе черновой каталог событий, выбор носителя с причинами, вилка бюджета. Вычитается из первого этапа, если продолжаете с нами.

200 000 ₽ · 2–3 недели

Обследование: несколько систем

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

350 000 ₽ · 3–4 недели

Пилот: один поток на событиях

Выбираем один поток, где сбой дороже всего, выносим его на события, запускаем рядом со старым путём и сравниваем результаты. Каталог, панель недоставленных, оповещения.

150 000–400 000 ₽ · 2–8 недель

Пилот: несколько потоков и координатор

Процесс с откатами и сроками, интеграция с боевыми системами, нагрузочная проверка, регламент разбора сбоев.

400 000–1 200 000 ₽ · 2–3 месяца

Вывод в эксплуатацию

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

от 1 500 000 ₽ этапами по 300 000–700 000 ₽ · от 3 месяцев

Сопровождение

Мониторинг очередей, разбор недоставленного, плановые доработки с пакета на 60 000 ₽. Три пакета: до 4, 12 или 25 часов в месяц; сверх пакета 5 000 ₽ в час.

20 000 / 60 000 / 120 000 ₽ в месяц
Процесс

Как идёт работа

2–3 недели — Обследование: потоки и сбои

Берём журналы за пиковые дни, список инцидентов и схему вызовов. Отмечаем, где сбой одной части останавливает другую.

Перед разработкой — Каталог и контракты

Названия, поля, версии, ключи порядка, срок хранения.

Параллельно — Стенд и сценарии отказов

Поднимаем носитель на тестовом стенде, пишем сценарии: обработчик упал, сообщение пришло дважды, события перепутались, носитель недоступен.

От 2 недель — Перевод одного потока

Новый путь работает рядом со старым, результаты сравниваются. Откат на старый путь возможен до приёмки.

Перед запуском — Панель и оповещения

Глубина очередей, возраст старейшего сообщения, число недоставленных, время цепочки. Порог оповещения согласуем с вами.

Дальше — Остальные потоки или остановка

Переводим следующий поток, только если первый показал пользу. Остановиться можно сразу после пилота: сделанное остаётся у вас.

Цена

Что двигает стоимость внутри вилки

Число читателей и систем

Каждая система добавляет контракт, тесты и сценарии отказов. Закрытая учётная система без интерфейса стоит дороже открытой.

Требования к порядку и точности

События, где порядок не важен, дешёвые. Деньги, остатки и статусы требуют ключей порядка, проверки версий и сверки с учётом.

Состояние текущей системы

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

Кто будет эксплуатировать

Очередь в вашей базе не требует нового сервиса. Брокер или журнал событий требуют мониторинга и человека, который их знает.

Контроль

Что остаётся у вас и чего мы не делаем

Остаётся у вас

  • Исходный код в вашем репозитории с первого коммита.
  • Каталог событий и схемы в репозитории, проверка совместимости в сборке.
  • Инструкция по восстановлению и повторной обработке недоставленного.
  • Право остановиться после любой ступени: сделанное остаётся у вас по акту.

Мы этого не делаем

  • Не делаем «шину предприятия» на все системы сразу: берём один поток и показываем, что он дал.
  • Не дежурим круглосуточно и не отвечаем за очереди после сдачи без пакета сопровождения; доступность 24/7 в ваших договорах учитывайте отдельно.
  • Не держим брокер у себя как услугу хостинга: он работает на вашем сервере или у выбранного вами хостера.
  • Не проводим аттестацию, лицензии ФСТЭК у нас нет; при требованиях к контуру их определяет заказчик.
Инструменты

На чём собираем

Серверная часть и носители
  • Python или Go, при необходимости Java по вашему стеку
  • PostgreSQL как источник истины и очередь для небольших потоков
  • RabbitMQ для задач и событий с маршрутизацией; журнал событий вроде Kafka только когда нужен повторный разбор истории
  • Redis для блокировок и кэша
Контракты, проверка, наблюдение
  • JSON Schema или Protobuf для описания событий, проверка совместимости в CI
  • Идентификатор цепочки в каждом событии и журнале
  • Метрики очередей и панели в вашей системе мониторинга
  • Docker, Nginx, GitLab CI, развёртывание в вашем контуре
Материалы

Материалы по теме

Статья

Событийная архитектура: что это и когда нужна

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

  • Событие и вызов
  • Когда окупается
  • Признаки, что рано
Читать →
Статья

Микросервисы или монолит: как выбрать архитектуру

Что платите за распределённость и как резать систему по понятиям. Пригодится, если события нужны между частями одной большой системы.

  • Цена распределённости
  • Границы по понятиям
  • Переход без остановки
Читать →
Статья

Проектирование API для интеграций: версии и ошибки

Повторы запросов, ключ идемпотентности, вебхуки с повторной доставкой. Те же принципы применимы к событиям.

  • Идемпотентность
  • Версии
  • Вебхуки
Читать →
Заявка

Разберём ваши потоки и скажем, где события окупятся

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

Спасибо, заявка отправлена. Ответим в течение рабочего дня.

Не удалось отправить. Напишите в телеграм или позвоните.

Вопросы

Частые вопросы: event-driven системы

Сколько стоит разработка event-driven системы?

Цена зависит от числа потоков и систем, а не от самой технологии. Обследование потоков стоит 200 000 ₽ за один-два процесса и 350 000 ₽ за несколько систем; оно вычитается из первого этапа, если продолжаете с нами. Пилот одного потока 150 000–400 000 ₽, тяжёлый пилот с координатором и боевой интеграцией 400 000–1 200 000 ₽. Вывод в эксплуатацию от 1 500 000 ₽ этапами по 300 000–700 000 ₽.

Нужны ли события, если у нас один монолит?

Часто достаточно очереди фоновых задач внутри приложения и чётких границ модулей. Событийная схема между частями монолита оправдана, когда отдельные модули должны реагировать на изменения других, не зная о них, и когда вы готовитесь выделять сервисы. Обследование покажет, какой из случаев ваш.

Что выбрать: RabbitMQ, Kafka или очередь в PostgreSQL?

Для небольшого потока и команды без опыта с брокерами начинаем с очереди в PostgreSQL: не нужен новый сервис, запись события и данных идёт в одной транзакции. Для задач с маршрутизацией и повторами берём RabbitMQ. Журнал вроде Kafka рассматриваем, когда нужны большой поток, несколько независимых читателей и повторный разбор истории.

Как не получить дубли и потери сообщений?

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

Как найти причину, если заказ завис посреди цепочки?

В каждое событие и запись журнала кладём идентификатор цепочки, по нему собирается путь заказа через все части. Для главных процессов добавляем сквозную трассировку и панель с возрастом старейшего необработанного сообщения. Без этой наблюдаемости распределённую систему мы не сдаём.

Где будет работать система и кому принадлежит код?

На ваших серверах или у выбранного вами хостера. Исходный код, каталог событий и инструкции в вашем репозитории с первого коммита. Результат работ передаётся вам по акту; порядок передачи прав записывается в договоре; открытые компоненты остаются под своими лицензиями, их список входит в документацию.

Как устроено сопровождение?

Ежемесячно: 20 000, 60 000 или 120 000 ₽ в месяц, до 4, 12 или 25 часов. Часы уходят на мониторинг очередей, разбор недоставленного и обращения; плановые доработки входят с пакета на 60 000 ₽. Сверх пакета 5 000 ₽ в час, по согласованию до начала.

Вы работаете только в Ростове-на-Дону?

Мы в Ростове-на-Дону, работаем с заказчиками по всей России: удалённо и в вашем контуре. Доступ к системам и обследование согласуем на старте с вашей службой безопасности.