Асинхронное взаимодействие разрывает отправителя и получателя во времени: producer публикует сообщение и не ждёт немедленного ответа. Для системного аналитика это ключевой способ описать интеграцию между сервисами, когда синхронный вызов (03.05.02 Синхронные протоколы (REST, gRPC, GraphQL)) создаёт жёсткую связность или не выдерживает пиковые нагрузки. В этой статье — модели доставки, гарантии как требования, выбор между очередью и логом, спецификация Producer/Consumer и граница с событийной архитектурой.

1. Суть темы

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

Асинхронный обмен через брокер сообщений (message broker) решает часть этих проблем:

  • Временная развязка (temporal decoupling) — отправитель может послать сообщение, когда получатель не в сети; получатель обработает его позже.
  • Пространственная развязка (spatial decoupling) — отправителю не нужен сетевой адрес получателя; достаточно имени очереди или топика в рамках согласованной конвенции (кластер, vhost, окружение, ACL — см. 03.05.05 Безопасность API).
  • Буферизация пиков — брокер накапливает сообщения, сглаживая нагрузку.
  • Настраиваемая надёжность доставки — брокер может хранить сообщение и повторять попытки, но конкретная семантика (at-most-once, at-least-once, exactly-once) задаётся конфигурацией и контрактом, а не самим фактом использования очереди.
  • Горизонтальное масштабирование — несколько экземпляров consumer'ов параллельно обрабатывают поток (competing consumers).

Компромиссы тоже явные: дополнительная инфраструктура, выше задержка (latency), сложнее сквозная observability (нужны traceId / correlationId — см. раздел 3), сложнее согласованность данных без явных требований (Outbox, Saga — см. 03.05.06 Event-Driven Architecture, 03.06.01 Архитектурные паттерны распределённых систем).

Асинхронность — часть интеграционного ландшафта наряду с API-led и шинами (03.05.01 Виды интеграции (точка-точка, ESB, API-led)). Аналитик взвешивает компромиссы через нефункциональные требования к производительности, надёжности и связанности.

Когда async, когда sync

Бизнес-ситуацияПредпочтительный подход
Быстрая операция, нужен немедленный ответСинхронный API (03.05.02 Синхронные протоколы (REST, gRPC, GraphQL))
Длительная обработка (> нескольких секунд)Очередь задач
Несколько сервисов независимо реагируют на один фактPub/sub, событийный топик
Нужна история событий, повторное чтение, потоковая аналитикаLog/stream (Kafka)
Нужен ответ, но без удержания HTTP-соединенияRequest–Reply over messaging (временная reply-очередь + correlationId)
Критична строгая консистентность «здесь и сейчас» без компенсацийСинхронный вызов
Нужна согласованность через несколько async-шагов с компенсациямиSaga — явно асинхронная модель, не замена sync «в лоб»

Комбинированный паттерн API + domain events часто работает так: REST принимает команду и возвращает идентификатор; результат и побочные эффекты рассылаются асинхронно. Webhook/callback для уведомления партнёра — по сути синхронный HTTP-вызов «в обратную сторону» (07.02 Работа с API (практика)), а не брокер сообщений.

Request–Reply over messaging — отдельный случай: клиент кладёт запрос в очередь с correlationId и replyTo; обработчик отвечает в указанную reply-очередь с тем же ID. Это async по транспорту, но контракт предполагает ответ — фиксируйте таймаут ожидания, формат ответа и семантику at-least-once (дубликаты ответа).

2. Ключевые понятия

Участники и артефакты

  • Сообщение (Message) — единица данных: заголовки (метаданные) и тело (полезная нагрузка: JSON, Avro, Protobuf). Формат обёртки (CloudEvents и др.) — 03.05.03 Спецификации контрактов (OpenAPI, AsyncAPI, Protobuf).
  • Брокер сообщений (Message Broker) — промежуточное ПО, принимающее, сохраняющее и доставляющее сообщения (RabbitMQ, Apache Kafka, AWS SQS и др.).
  • Producer (издатель) — приложение, публикующее сообщения.
  • Consumer (потребитель) — приложение, получающее и обрабатывающее сообщения.
  • Consumer Group (Kafka) — группа consumer'ов, совместно читающих топик; каждое сообщение в группе обрабатывается одним consumer'ом.
  • Poison message (poison pill) — сообщение, которое consumer не может обработать даже после всех retry (битая схема, невалидные данные, постоянная ошибка бизнес-логики); после исчерпания попыток уходит в DLQ.

Очередь vs топик vs log — не путать модели

МодельСемантикаКто получает сообщениеПосле чтения
Очередь (queue) — point-to-pointЗадача на обработкуОдин consumer из группы (competing consumers)Сообщение удаляется (RabbitMQ ack, SQS delete)
Pub/sub (топик)Широковещательное событиеКаждый подписчик / consumer groupЗависит от брокера
Log/stream (Kafka)Append-only журналКаждая consumer group читает независимоСообщение остаётся до истечения retention

RabbitMQ — классические очереди и гибкая маршрутизация (direct, topic, fanout). Fanout доставляет всем подписчикам; очередь с competing consumers — только одному worker'у. Kafka — распределённый log: сообщения не удаляются после чтения, позиция фиксируется offset'ом; «все сервисы видят событие» достигается разными consumer group, а «ровно один обработчик» — одной group с competing consumers.

Модели доставки

Point-to-Point (очередь): producer → очередь → один consumer забирает и подтверждает (ack). Сообщение исчезает.

sequenceDiagram
    participant P as Producer
    participant B as Broker (Queue)
    participant C1 as Consumer 1
    participant C2 as Consumer 2

    P->>B: Отправить сообщение
    B-->>P: Publisher confirm
    C1->>B: Получить сообщение
    B->>C1: Сообщение
    C1->>C1: Обработка
    C1->>B: Ack
    Note over B: Сообщение удалено

Publish-Subscribe: producer публикует в топик; каждая consumer group (Kafka) или каждый подписчик (RabbitMQ fanout) получает копию.

sequenceDiagram
    participant P as Producer
    participant B as Broker (Topic)
    participant C1 as Consumer Group A
    participant C2 as Consumer Group B

    P->>B: Опубликовать событие
    B->>C1: Доставить
    B->>C2: Доставить (независимо)
    C1-->>B: Ack / commit offset
    C2-->>B: Ack / commit offset

Команда vs событие

Центральное различение для именования и контрактов. Подробнее о паттернах EDA — 03.05.06 Event-Driven Architecture; о domain events — 03.06.02 Основы Domain-Driven Design.

Команда (Command)Событие (Event)
СмыслПросьба выполнить действиеФакт, который уже произошёл
ИменованиеCreateOrder, ProcessPaymentOrderCreated, PaymentReceived
ВремяImperativePast tense
ПолучателиОбычно один исполнительНоль или много подписчиков
ИдемпотентностьОбязательна при at-least-onceОбязательна при at-least-once
Типичный каналОчередь задачТопик / log

Domain event vs integration event

Domain event — факт внутри bounded context; владелец схемы — команда домена. Integration event — сообщение на границе сервисов: что именно уходит в топик/очередь, в какой схеме, кто владелец контракта.

В спецификации интеграционного контракта аналитик фиксирует:

  • имя топика/очереди и eventType (integration event, не внутренний domain event);
  • минимальный payload для потребителей (часто подмножество domain-модели);
  • владельца схемы и правило эволюции (03.05.07 Управление версионированием API и жизненным циклом);
  • mapping: какой domain event порождает какой integration event и при каком бизнес-триггере.

Детали DDD — 03.06.02 Основы Domain-Driven Design; паттерны публикации — 03.05.06 Event-Driven Architecture.

Основные технологии (обзор для выбора модели)

RabbitMQ (AMQP): exchange → binding → queue; publisher confirms, manual ack/nack; типично — фоновые задачи, отложенная обработка.

Apache Kafka: топики с партициями, append-only log; порядок только внутри партиции; retention и compaction — отдельные NFR (важно для PII/GDPR — см. 08.02 Нефункциональные требования (НФТ) — классификация и стандарты); сценарии — event streaming, несколько независимых потребителей, повторное чтение.

Управляемые очереди (AWS SQS/SNS, Google Pub/Sub, Azure Service Bus): SQS — point-to-point, at-least-once; consumer удаляет сообщение (DeleteMessage) после успеха; при сбое до delete — повторная доставка после visibility timeout. FIFO-очередь гарантирует порядок в рамках message group (аналог partition key в Kafka).

3. Как применяется в работе аналитика

3.1. Гарантии доставки — формулировать как требования

Асинхронность не даёт «гарантированную доставку» по умолчанию. Семантику нужно явно указать в спецификации:

СемантикаПоведениеКогда выбирать
At-most-onceМожет потеряться, не дублируетсяМетрики, логи, некритичные уведомления
At-least-onceДоставится, возможны дубликатыБольшинство бизнес-потоков; consumer обязан быть идемпотентным
Exactly-once processingРовно одна обработка сообщения на стороне consumerДеньги, инвентарь; дедуп по messageId, идемпотентный consumer, транзакции Kafka, idempotent producer

Exactly-once processing — про семантику доставки и обработки: как гарантировать, что одно и то же сообщение не применится дважды (хранилище обработанных ID, transactional consume–produce в Kafka). Это не то же самое, что Saga или Outbox.

Бизнес-инварианты «без двойного списания» через несколько систем — отдельный слой требований:

ПроблемаПаттерн в требованиях
Запись в БД и публикация в брокер должны быть атомарныTransactional Outbox
Цепочка async-шагов без единой транзакции, но с компенсациями при сбоеSaga (choreography или orchestration)
Финансы, резерв запасов, любой сценарий «двойного списания» недопустимOutbox + идемпотентность + при необходимости Saga

Outbox и Saga решают распределённую согласованность процесса, а не заменяют формулировку exactly-once processing в спецификации consumer'а. Детали — 03.05.06 Event-Driven Architecture.

Для at-least-once зафиксируйте в критериях приёмки consumer'а: повторная обработка того же messageId не создаёт дублирующих побочных эффектов.

3.2. Порядок сообщений (ordering)

Вопрос в спецификацииРешение
Нужен строгий порядок для одной сущности?Partition key = orderId (Kafka) или FIFO queue / message group (SQS)
Можно обрабатывать параллельно?Несколько партиций / competing consumers без общего ключа
Порядок между разными сущностями?Обычно не требуется
Глобальный порядок по всему топику?Не гарантируется — только внутри ключа / партиции / message group

Если порядок критичен — явно запретите параллельную обработку сообщений одного ключа.

3.3. Контракт async-интерфейса: AsyncAPI и схемы

Специфицируйте асинхронные интерфейсы не менее детально, чем REST. Формат контракта — 03.05.03 Спецификации контрактов (OpenAPI, AsyncAPI, Protobuf): AsyncAPI для Kafka/RabbitMQ наравне с OpenAPI для REST; единый envelope (CloudEvents) — там же. Схема сообщения — JSON Schema или Avro; правила эволюции — 03.05.07 Управление версионированием API и жизненным циклом.

Готовые шаблоны в базе (10.04 Чеклист аналитика перед стартом разработки — проверка полноты перед стартом):

Producer — обязательные поля спецификации:

  • Бизнес-триггер публикации
  • Топик/очередь (имя, тип, окружение)
  • Схема сообщения и версия (schemaVersion)
  • Ключ партиционирования / routing key
  • Заголовки: traceId, correlationId, eventType, messageId
  • Семантика доставки и идемпотентность producer'а (не только consumer'а)
  • Обработка ошибок отправки (retry, circuit breaker)

Consumer — обязательные поля:

  • Источник, consumer group / имя очереди
  • Логика обработки (шаги)
  • Идемпотентность: ключ дедупликации, хранилище обработанных ID
  • Retry: max attempts, backoff, poison message → DLQ
  • DLQ: владелец очереди, runbook, SLA ручной обработки, алертинг
  • Подтверждение: Kafka — commit offset после успеха; RabbitMQ — ack/nack; SQS — DeleteMessage после успеха, visibility timeout при длительной обработке
  • Пропускная способность, допустимый consumer lag

NFR-чеклист для async (дополняет NFR или companion к AsyncAPI):

Вопросы на elicitation (владельцы и процессы):

3.4. Паттерны интеграции (EIP)

Из Enterprise Integration Patterns (Hohpe, Woolf). Укажите в требованиях где фиксируется каждый паттерн:

ПаттернГде в артефактах
Competing ConsumersConsumer spec, NFR (throughput)
Dead Letter Queue (DLQ)Consumer spec + runbook (владелец, SLA)
Retry with BackoffConsumer spec (max attempts, backoff)
Correlation IdentifierProducer/Consumer spec (заголовки)
Message Expiry (TTL)Producer spec или NFR (retention)

3.5. Пример спецификации потока

Producer: OrderCreated

  • Топик: shop.order.events (Kafka) / очередь shop.order.created (RabbitMQ)
  • AsyncAPI: см. репозиторий контрактов, версия 1.2.0
  • Схема: {"orderId": "uuid", "customerId": "uuid", "totalAmount": decimal, "createdAt": "ISO8601"}
  • Ключ партиции / routing key: orderId
  • Семантика: at-least-once; producer — idempotent; consumer проверяет orderId в таблице обработанных

Вариант A — Kafka (commit offset после успеха):

sequenceDiagram
    participant OrderSvc as Order Service
    participant Broker as Kafka
    participant Billing as Billing Service
    participant DLQ as Dead Letter Topic

    OrderSvc->>OrderSvc: Создать заказ (БД)
    OrderSvc->>Broker: OrderCreated
    Broker-->>OrderSvc: Ack (producer confirm)
    Broker->>Billing: Доставка
    Billing->>Billing: Дедупликация, оплата
    alt Успех
        Billing->>Broker: Commit offset
    else Ошибка после retry
        Billing->>DLQ: Poison message
    end

Вариант B — RabbitMQ (manual ack после успеха):

sequenceDiagram
    participant OrderSvc as Order Service
    participant Broker as RabbitMQ
    participant Billing as Billing Service
    participant DLQ as Dead Letter Queue

    OrderSvc->>OrderSvc: Создать заказ (БД)
    OrderSvc->>Broker: OrderCreated
    Broker-->>OrderSvc: Publisher confirm
    Broker->>Billing: Доставка
    Billing->>Billing: Дедупликация, оплата
    alt Успех
        Billing->>Broker: Ack
    else Ошибка после retry
        Billing->>Broker: Nack → DLQ
    end

Моделируйте потоки через 03.02.03 Диаграмма последовательности (Sequence Diagram); параллельные ветки и таймеры — 03.02.02 Диаграмма деятельности (Activity Diagram).

3.6. Матрица выбора модели

Бизнес-вопросОчередь (task)Log/stream (Kafka)Pub/sub (fanout / несколько groups)
Обработать задачу ровно одним worker'ом✓ (competing consumers)✓ (одна consumer group)✗ — доставка всем подписчикам; для «одного worker'а» нужна очередь или одна group, не fanout
Все сервисы видят одно событие✗✓ (разные consumer groups)✓ (fanout / отдельные подписки)
Перечитать историю / replay✗✓✗
Простота, классические фоновые job'ы✓ (RabbitMQ, SQS)избыточно—
Строгий порядок по ключуFIFO SQS / одна очередьpartition key (порядок внутри партиции)зависит от брокера

3.7. Верификация и приёмка async-интеграции

Асинхронный контракт проверяется не только «на staging вручную». Зафиксируйте в V&V и тестировании требований:

  • Контрактные тесты producer/consumer по AsyncAPI и схеме (совместимость версий).
  • Тестовый топик/очередь — изоляция от prod, воспроизводимые сценарии.
  • Симуляция duplicate delivery — повторная обработка того же messageId не ломает инварианты.
  • Сценарий «consumer отстаёт» — consumer lag выше порога, алерт, поведение при backlog.
  • Poison message — сообщение уходит в DLQ по критериям из spec; владелец обрабатывает по runbook.

Критерии приёмки — 02.04.02 Критерии приёмки (Acceptance Criteria).

4. Типовые ошибки и ограничения

  • «Очередь = гарантированная доставка» — без явной семантики и ack/nack или publisher confirms доставка не гарантирована.
  • Путаница RabbitMQ queue и Kafka topic — «один раз обработать» vs «все подписчики видят»; проверяйте модель до выбора технологии.
  • Смешение fanout и competing consumers — pub/sub доставляет всем; «ровно один worker» — очередь или одна Kafka consumer group.
  • Exactly-once = Saga — Saga — про многошаговый процесс с компенсациями; exactly-once processing — про дедупликацию обработки одного сообщения.
  • Commit offset / ack / DeleteMessage до завершения обработки — потеря сообщения при сбое; фиксируйте порядок в спецификации consumer'а.
  • Идемпотентность только на consumer'е — при at-least-once нужен и идемпотентный producer (дубли при retry отправки).
  • Событие названо как команда — CreateOrder в топике с несколькими подписчиками создаёт неоднозначность; используйте past tense для фактов.
  • Domain event напрямую в топик — без явного integration event и владельца схемы ломается эволюция контракта.
  • Раздувание § EDA в интеграционной статье — Event Sourcing, CQRS детально живут в 03.05.06 Event-Driven Architecture, не дублируйте.
  • Пример только под Kafka — для RabbitMQ и SQS другая модель подтверждений; указывайте брокер проекта.
  • DLQ без процесса — очередь-помойка без владельца, SLA и алертов бесполезна.
  • Retention и PII — в Kafka сообщения хранятся; для персональных данных задайте retention, шифрование, политику удаления (08.05 Управление рисками на этапе анализа).
  • Webhook вместо очереди у партнёра — callback — синхронный HTTP; для надёжности нужны retry, idempotency key на стороне получателя; при нестабильном партнёре — очередь между вашей системой и исходящим webhook'ом, чтобы не терять события при его недоступности.
  • Fire-and-forget vs request–reply — если бизнес ждёт ответ, но вы описали только publish без reply-очереди и correlationId, контракт неполон.

Связи с другими разделами базы

Источники и дополнительные материалы

Книги и стандарты

  • Hohpe, G., Woolf, B. (2003). Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Addison-Wesley.
  • Kleppmann, M. (2017). Designing Data-Intensive Applications. O'Reilly. — Главы о гарантиях доставки и потоковой обработке.
  • Newman, S. (2015). Building Microservices. O'Reilly. — Асинхронная интеграция в микросервисах.
  • Richardson, C. (2019). Microservices Patterns. Manning. — Saga, Outbox, Event Sourcing, CQRS.

Спецификации и документация

Дополнительно