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

105 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
2. `omni_chat`
- Потребляет входящие события из `receiver.flow`.
- Разрешает идентичности и треды.
- Создает/обновляет `OmniMessage`, `OmniThread`, статусы и доменные эффекты.
- Формирует исходящие команды и кладет их в `sender.flow`.
3. `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`.