Событийная архитектура

Событийная архитектура (в англ литературе event-driven architecture. Сокращенно EDA) — один из самых распространенных видов интеграций в современной архитектуре. Без нее очень сложно реализовать удобный flow и повысить UX пользователя. Именно этот вид архитектуры используется при загрузке видео на YouTube, при обработке платежей и многих других действиях, где не требуется прямое участие пользователя — то есть можно «отпустить» его делать что-то еще и не блокировать спиннером на экране.

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

Сейчас люди генерируют очень много контента и выкладывают его в сеть. Почему же привычная схема «запрос — ответ» здесь не подходит?

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

Что в это время происходит под капотом: 

  1. Сначала идет отправка запроса на загрузку видео.
  2. Сервер получает запрос и возвращает пользователю уведомление, что всё ОК.
  3. Далее видео отправляется на проверку запрещённого контента.
  4. После этого идёт проверка авторских прав.
  5. Затем происходит генерация субтитров.
  6. Ещё один сервис обрабатывает видео и формирует разные варианты качества —  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 рискует обработать одно и то же событие несколько раз. Если события не идемпотентны, это может исказить состояние. Вот несколько примеров, что может случиться: 

  1. Повторно увеличим баланс клиента.
  2. Повторно запостится история.
  3. Поставится 2 лайка под стори, а не один.

Вот несколько вариантов решений, которые помогут избежать этой ошибки.

1. Идемпотентные события и обработчики

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

Как это сделать:

  • Присваивать каждому событию уникальный идентификатор (event ID).
  • В consumer проверять, было ли это событие уже обработано (например, вести таблицу «processed_events»). Если да — игнорировать. Если нет — обрабатывать и ставить пометку «обработано».
  • Структурировать логику так, чтобы повторное выполнение не приводило к дублированию. Например, «установить статус=PAID» вместо «статус += 1». Таким образом, наша система проверит, была ли уже у этого платежа обработка или нет.

2. Дедупликация (Deduplication)

Суть: активно отсеивать дубликаты событий.

Как это сделать:

  • Сделать настройку на уровне брокера (некоторые системы, например, Kafka, поддерживают механизмы для дедупликации на продюсере или в топике).
  • На уровне консьюмера — хранить «хеш» или ID уже виденных событий.

Плюсы: повышает устойчивость системы к повторам.

Минусы: требует дополнительного хранения метаданных (списка уже обработанных событий).

3. Использование транзакций и Exactly-Once Semantics (если брокер поддерживает эту механику) 

  • Kafka имеет режим транзакций и «идемпотентных продюсеров».
  • Однако реализация «ровно одного» часто сложна и влияет на производительность.

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