/
Praxis/

Зачем Event-Driven Architecture?

Контекст

Прежде чем добавлять RabbitMQ и Worker, важно понять: зачем вообще нужна асинхронная коммуникация? Синхронный HTTP работает — почему бы не продолжать его использовать? Event-Driven Architecture не только решает конкретные проблемы, но создаёт новые. Понимание сильных и слабых сторон подхода — ключевой навык архитектора.

Реальные примеры из индустрии

YouTube — когда вы загружаете видео, оно не появляется мгновенно. YouTube принимает файл и ставит его в очередь на обработку: конвертация в разные разрешения (360p, 720p, 1080p, 4K), создание миниатюр, проверка на копирайт. Это занимает минуты или даже часы, но пользователь не ждёт — он может закрыть страницу и вернуться позже.

Instagram — при публикации фото оно сразу появляется в ленте, но в низком качестве. В фоне Instagram создаёт версии для разных устройств, применяет CDN-кэширование, обновляет ленты подписчиков. Всё это происходит асинхронно.

Яндекс GO — когда вы заказываете такси, приложение не блокируется на 30 секунд, пока система ищет водителя. Заказ создаётся мгновенно, а поиск водителя происходит в фоне. Вы видите анимацию "Ищем водителя...", а система тем временем рассылает уведомления ближайшим водителям.

Ozon, WB — при оформлении заказа вы не ждёте, пока склад проверит наличие товара, курьерская служба назначит доставку, а платёжная система обработает транзакцию. Заказ принимается мгновенно, остальное — в фоне.

Проблемы синхронного подхода

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

1. Таймауты и каскадные сбои

Пользователь → API → Сервис A → Сервис B → Сервис C
                                           ↓
                                      (таймаут 30с)

Если Сервис C медленно отвечает, вся цепочка блокируется. Пользователь ждёт 30 секунд и получает ошибку. Хуже того: пока он ждёт, занят один thread/goroutine в каждом сервисе. При высокой нагрузке это приводит к исчерпанию ресурсов.

2. Tight Coupling (тесная связанность)

Сервис A знает адрес Сервиса B и зависит от его доступности. Если Сервис B упал — Сервис A не может работать. Это противоречит идее независимых сервисов.

3. Блокировка ресурсов

HTTP-соединение занято, пока идёт обработка. Если обработка длится 10 секунд — 10 секунд занят сетевой ресурс. При 1000 параллельных запросах нужно 1000 соединений.

4. Плохой UX для долгих операций

Пользователь не хочет смотреть на спиннер 30 секунд. Лучше показать "Запрос принят, результат будет готов через минуту" и освободить пользователя.

Event-Driven Architecture: решение

В Event-Driven Architecture сервисы общаются через посредника — Message Broker. Вместо прямого вызова сервис публикует сообщение (event) в очередь, а другой сервис его забирает и обрабатывает.

Синхронно:
Сервис A ──HTTP──► Сервис B (A ждёт ответа)

Асинхронно:
Сервис A ──►[Очередь]──► Сервис B (A не ждёт)

Преимущества:

  1. Loose Coupling (слабая связанность) — сервисы не знают друг о друге напрямую. Сервис A публикует сообщение "Загружено изображение", ему всё равно, кто его обработает.

  2. Отказоустойчивость — если Сервис B упал, сообщения накапливаются в очереди. Когда он поднимется — обработает всё. Ничего не потеряно.

  3. Масштабирование — можно запустить 10 workers для обработки очереди. Message Broker сам распределит сообщения.

  4. Fire-and-Forget — сервис отправил сообщение и забыл. Не нужно ждать ответа, держать соединение, обрабатывать таймауты.

  5. Пиковые нагрузки — очередь сглаживает пики. Если пришло 1000 запросов в секунду, они встанут в очередь и обработаются постепенно.

Ключевые концепции

/ Message Broker — посредник, который принимает сообщения от одних сервисов и доставляет другим. Примеры: RabbitMQ, Apache Kafka, Amazon SQS, Redis Streams. Это как почтовое отделение: отправитель оставляет письмо, получатель забирает когда удобно.

(отправитель) — сервис, который отправляет сообщения в очередь. В нашем случае books-service будет producer: он публикует сообщение "Нужно обработать изображение".

(потребитель) — сервис, который читает сообщения из очереди и обрабатывает их. В нашем случае worker-service будет consumer: он забирает сообщения и обрабатывает изображения.

Queue (очередь) — место хранения сообщений. Сообщения хранятся в очереди, пока consumer их не заберёт. Очередь гарантирует, что сообщение не потеряется.

Когда НЕ нужен Message Broker

Event-Driven Architecture — не серебряная пуля. Есть сценарии, где синхронный подход лучше:

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

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

Простой CRUD без тяжёлой обработки

Создание записи в базе занимает миллисекунды. Зачем городить очередь?

Транзакционная целостность

Нужно атомарно обновить несколько таблиц. С очередями это сложнее (нужны паттерны Saga, Outbox).

Маленькая команда и простая система

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

Наш учебный случай

В этом проекте мы используем Event-Driven Architecture для обработки обложек книг:

  1. Пользователь загружает изображение → books-service сохраняет оригинал в MinIO и публикует сообщение в RabbitMQ
  2. Worker-service забирает сообщение → скачивает оригинал, создаёт версии разных размеров, загружает обратно
  3. Worker обновляет статус → книга получает URL обложки

Пользователь не ждёт обработки — он сразу видит "Обложка обрабатывается" и может продолжить работу.

Цели этапа

Зачем: Зафиксировать архитектуру асинхронной обработки в README до написания кода — чтобы понимать, какие компоненты добавляются и как они взаимодействуют.

Что делаем:

Обновите README.md проекта. Добавьте в него:

  1. Новые компоненты — опишите три сервиса, которые добавляются в систему:
    • RabbitMQ — очередь сообщений для асинхронных задач
    • MinIO — S3-совместимое хранилище для файлов (обложки книг)
    • worker-service — фоновый обработчик задач из очереди
  2. Сценарий обработки обложки — опишите поток данных: загрузка → публикация в очередь → обработка worker'ом → сохранение результата
  3. Почему асинхронно — объясните, зачем выносить обработку изображений в фоновый процесс вместо синхронной обработки в запросе

Пример работы

Примерная структура обновлённого README:

## Асинхронная обработка ### Новые компоненты - **RabbitMQ** — очередь сообщений для асинхронных задач - **MinIO** — S3-совместимое хранилище для обложек книг - **worker-service** — фоновый обработчик задач из очереди ### Сценарий обработки обложки 1. Пользователь загружает изображение → books-service сохраняет оригинал в MinIO 2. books-service публикует сообщение в RabbitMQ 3. worker-service забирает сообщение, обрабатывает изображение (resize) 4. worker-service загружает результат в MinIO и обновляет статус в БД ### Почему асинхронно? Обработка изображений — тяжёлая операция (1-5 секунд). Если делать синхронно, пользователь будет ждать. Асинхронный подход: пользователь сразу получает ответ "обложка обрабатывается", а результат появляется в фоне.

Полезные материалы

  • Event-Driven Architecture — Мартин Фаулер
  • RabbitMQ Tutorials — официальные туториалы
  • Event-Driven Architecture — введение — на русском

Критерии

не проверялось
  • Файл README.md существует в корне проекта и содержит описание асинхронной архитектуры
  • В README.md описаны 3 новых компонента: RabbitMQ (очередь сообщений), MinIO (хранилище файлов), worker-service (фоновая обработка)
  • В README.md описан сценарий обработки обложки: загрузка → очередь → обработка → сохранение
Войдите в аккаунт, чтобы начать проект
Запустите первую проверку