Событийная архитектура (в англ литературе event-driven architecture. Сокращенно EDA) — один из самых распространенных видов интеграций в современной архитектуре. Без нее очень сложно реализовать удобный flow и повысить UX пользователя. Именно этот вид архитектуры используется при загрузке видео на YouTube, при обработке платежей и многих других действиях, где не требуется прямое участие пользователя — то есть можно «отпустить» его делать что-то еще и не блокировать спиннером на экране.
В этом разделе мы посмотрим, какие сложности могут быть и как не напороться на частые ошибки, которые разработчики допускают при проектировании архитектуры.
Сейчас люди генерируют очень много контента и выкладывают его в сеть. Почему же привычная схема «запрос — ответ» здесь не подходит?
Вот пользователь вашего мессенджера постит сторис.
Что в это время происходит под капотом:
- Сначала идет отправка запроса на загрузку видео.
- Сервер получает запрос и возвращает пользователю уведомление, что всё ОК.
- Далее видео отправляется на проверку запрещённого контента.
- После этого идёт проверка авторских прав.
- Затем происходит генерация субтитров.
- Ещё один сервис обрабатывает видео и формирует разные варианты качества — 1080, 720, 480 и так далее.
Такой процесс намного проще реализовать через EDA: создаётся event, и пользователь уже не участвует во внутреннем процессе. Он выкладывает сторис и теперь может идти писать сообщение, а в это время background система сама обработает его запрос и запостит историю.
Если рассматривать EDA в чистом виде, здесь есть три основных компонента:
- Producers — верхнеуровневый сервис или система (например, внешнее API, база данных с CDC, либо процесс в другом сервисе).
- Brokers — место, где сохраняются данные (events), поступающие от producers.
- Consumers — процесс, который считывает event из broker, обрабатывает его по своей логике и при необходимости сохраняет результаты либо вызывает дальнейшие сервисы.
У EDA большое количество паттернов, которые позволяют делать систему масштабируемой и надежной. Например, в мини-курсе по Kafka ты познакомишься c CQRS.
Давай посмотрим на одну из сложностей EDA (кстати, именно это любят спрашивать на собесах, когда разговор заходит про архитектуру).
Idempotency (Идемпотентность)
В распределённых системах нельзя полагаться только на «exactly-once processing» (гарантию ровно одного выполнения). Если consumer успешно обработал событие, но ack (подтверждение) потерялось по сети, брокер или продюсер могут повторно отправить то же самое событие.
В результате consumer рискует обработать одно и то же событие несколько раз. Если события не идемпотентны, это может исказить состояние. Вот несколько примеров, что может случиться:
- Повторно увеличим баланс клиента.
- Повторно запостится история.
- Поставится 2 лайка под стори, а не один.
Вот несколько вариантов решений, которые помогут избежать этой ошибки.
1. Идемпотентные события и обработчики
Суть: обеспечить то, чтобы повторная обработка одного и того же события не могла изменить состояние во второй (и каждый последующий) раз.
Как это сделать:
- Присваивать каждому событию уникальный идентификатор (event ID).
- В consumer проверять, было ли это событие уже обработано (например, вести таблицу «processed_events»). Если да — игнорировать. Если нет — обрабатывать и ставить пометку «обработано».
- Структурировать логику так, чтобы повторное выполнение не приводило к дублированию. Например, «установить статус=PAID» вместо «статус += 1». Таким образом, наша система проверит, была ли уже у этого платежа обработка или нет.
2. Дедупликация (Deduplication)
Суть: активно отсеивать дубликаты событий.
Как это сделать:
- Сделать настройку на уровне брокера (некоторые системы, например, Kafka, поддерживают механизмы для дедупликации на продюсере или в топике).
- На уровне консьюмера — хранить «хеш» или ID уже виденных событий.
Плюсы: повышает устойчивость системы к повторам.
Минусы: требует дополнительного хранения метаданных (списка уже обработанных событий).
3. Использование транзакций и Exactly-Once Semantics (если брокер поддерживает эту механику)
- Kafka имеет режим транзакций и «идемпотентных продюсеров».
- Однако реализация «ровно одного» часто сложна и влияет на производительность.
Давай посмотрим на архитектуру с подробными комментариями, как и где используется EDA.