Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
104
docs/adr/0001-chat-platform-service-boundaries.md
Normal file
104
docs/adr/0001-chat-platform-service-boundaries.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user