5.3 KiB
5.3 KiB
ADR-0001: Разделение Chat Platform на 3 сервиса
Дата: 2026-02-21 Статус: accepted
Контекст
Сейчас delivery уже вынесен отдельно, но часть omni-интеграции остается в приложении frontend.
Нужна архитектура, где входящие вебхуки, доменная логика чатов и исходящая доставка развиваются независимо и не ломают друг друга.
Критичные требования:
- входящие webhook-события не теряются при рестартах;
- delivery управляет retry/rate-limit централизованно;
- omni_chat остается единственным местом доменной логики и хранения состояния диалогов;
- сервисы можно обновлять независимо.
Решение
Принимаем разделение на 3 сервиса:
omni_inbound
- Принимает вебхуки провайдеров.
- Валидирует подпись/секрет.
- Нормализует событие в универсальный envelope.
- Пишет событие в durable queue (
receiver.flow) с идемпотентнымjobId. - Возвращает
200только после успешной durable enqueue. - Не содержит бизнес-логики CRM.
omni_chat
- Потребляет входящие события из
receiver.flow. - Разрешает идентичности и треды.
- Создает/обновляет
OmniMessage,OmniThread, статусы и доменные эффекты. - Формирует исходящие команды и кладет их в
sender.flow.
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.
Обязательные поля:
versionidempotencyKeyprovider,channel,directionproviderEventId,providerMessageIdeventType,occurredAt,receivedAtpayloadRaw,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 при инцидентах;
- проще подключать новые каналы поверх общего контракта.
Минусы:
- больше инфраструктурных компонентов (очереди, мониторинг, трассировка);
- требуется дисциплина по контрактам между сервисами.
План внедрения
- Вводим
omni_inboundкак отдельный сервис для Telegram Business. - Потребление
receiver.flowреализуем вomni_chat. - Текущее исходящее API оставляем за
omni_outbound. - После стабилизации выносим оставшиеся omni endpoint'ы из
frontendвomni_chat/omni_inbound.