Files
clientsflow/docs/adr/0001-chat-platform-service-boundaries.md

5.3 KiB
Raw Blame History

ADR-0001: Разделение Chat Platform на 3 сервиса

Дата: 2026-02-21 Статус: accepted

Контекст

Сейчас delivery уже вынесен отдельно, но часть omni-интеграции остается в приложении frontend. Нужна архитектура, где входящие вебхуки, доменная логика чатов и исходящая доставка развиваются независимо и не ломают друг друга.

Критичные требования:

  • входящие webhook-события не теряются при рестартах;
  • delivery управляет retry/rate-limit централизованно;
  • omni_chat остается единственным местом доменной логики и хранения состояния диалогов;
  • сервисы можно обновлять независимо.

Решение

Принимаем разделение на 3 сервиса:

  1. omni_inbound
  • Принимает вебхуки провайдеров.
  • Валидирует подпись/секрет.
  • Нормализует событие в универсальный envelope.
  • Пишет событие в durable queue (receiver.flow) с идемпотентным jobId.
  • Возвращает 200 только после успешной durable enqueue.
  • Не содержит бизнес-логики CRM.
  1. omni_chat
  • Потребляет входящие события из receiver.flow.
  • Разрешает идентичности и треды.
  • Создает/обновляет OmniMessage, OmniThread, статусы и доменные эффекты.
  • Формирует исходящие команды и кладет их в sender.flow.
  1. omni_outbound
  • Потребляет sender.flow.
  • Выполняет отправку в провайдеров (Telegram Business и др.).
  • Управляет retry/backoff/failover, DLQ и статусами доставки.
  • Не содержит UI и доменной логики чатов.

Почему webhook и delivery разделены

  • Входящий контур должен отвечать быстро и предсказуемо.
  • Исходящий контур живет с долгими retry и ограничениями провайдера.
  • Сбой внешнего API не должен блокировать прием входящих сообщений.

Границы ответственности

omni_inbound:

  • можно: auth, валидация, нормализация, дедуп, enqueue;
  • нельзя: запись доменных сущностей CRM, принятие продуктовых решений.

omni_chat:

  • можно: вся доменная модель чатов, orchestration, бизнес-правила;
  • нельзя: прямые вызовы провайдеров из sync API-контекста.

omni_outbound:

  • можно: провайдерные адаптеры, retry, rate limits;
  • нельзя: резолвинг бизнес-правил и маршрутизации диалога.

Универсальный протокол событий

Внутренний контракт входящих событий: docs/contracts/omni-inbound-envelope.v1.json.

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

  • version
  • idempotencyKey
  • provider, channel, direction
  • providerEventId, providerMessageId
  • eventType, occurredAt, receivedAt
  • payloadRaw, payloadNormalized

Идемпотентность и надежность

  • jobId в очереди строится из idempotencyKey.
  • Дубликаты входящих webhook событий безопасны и возвращают 200.
  • 200 от omni_inbound отдается только после успешного добавления в Redis/BullMQ.
  • При ошибке durable enqueue omni_inbound возвращает 5xx, провайдер выполняет повторную доставку.
  • Базовые рабочие очереди: receiver.flow и sender.flow; технические очереди для эскалации: receiver.retry, sender.retry, receiver.dlq, sender.dlq.

Последствия

Плюсы:

  • независимые релизы и масштабирование по ролям;
  • меньше blast radius при инцидентах;
  • проще подключать новые каналы поверх общего контракта.

Минусы:

  • больше инфраструктурных компонентов (очереди, мониторинг, трассировка);
  • требуется дисциплина по контрактам между сервисами.

План внедрения

  1. Вводим omni_inbound как отдельный сервис для Telegram Business.
  2. Потребление receiver.flow реализуем в omni_chat.
  3. Текущее исходящее API оставляем за omni_outbound.
  4. После стабилизации выносим оставшиеся omni endpoint'ы из frontend в omni_chat/omni_inbound.