Перейти к основному содержимому

ADR-0055: Собственный мини-мессенджер PIN; оркестрация чат↔сделка в CRM-событиях

Статус

Accepted

Дата

2026-07-13

Контекст

ТЗ.md:287–309 требует мессенджер под нужды платформы: личные сообщения, контакты и звонки, чёрный список, чаты сделок с авто-событиями/карточками (целевые действия «загрузите документы», инфо «договор оплачен»), per-participant видимость сообщений, правка/удаление ≤1 минуты с хранимой историей правок, IP/user-agent отправителя. Мессенджер строится с нуля под нужды платформы; фронт ждёт наш контракт. Открытый вопрос ТЗ.md:301 — «модуль в сервисе сделок или микросервис-оркестратор».

Решение

  1. Пишем собственный мини-мессенджер — контекст messenger (.NET, поверх Talent.Common.*), без внешнего чат-сервера. Хранение — PostgreSQL (наши агрегаты Chat/Message/Participant/ChatEvent), доставка в реальном времени — SignalR (WebSocket) на базе realtime-блока SDK (docs/11-backend-sdk/10-signalr-realtime.md). На целевой нагрузке (1–10k активных, ADR-0056) достаточно одного инстанса без Redis backplane; backplane — опция при реальном масштабировании.
  2. Scope мини-мессенджера (под наши нужды, не general-purpose):
    • личные чаты 1:1 (по контактам), групповые чаты лидов (ChatKind.Lead) и чаты сделок (ChatKind.Deal);
    • типизированные сообщения (TPH): текст, вложение, событие/карточка (целевое действие / инфо) с per-participant видимостью и per-participant текстами;
    • контакты (заявка→подтверждение), чёрный список, поиск по телефону;
    • правка/удаление ≤1 мин; история правок хранится (не показывается); IP/UA на сообщении;
    • read-receipts/непрочитанные — счётчики для UI; уведомления через notifications;
    • звонки (аудио/видео) — отдельная фаза: в ТЗ фиксируется только контракт сигналинга поверх контактов; media-стек (WebRTC/SFU) выбирается при реализации.

      Superseded (ADR-0062). Media-стек выбран: самостоятельно разворачиваемый Jitsi (JVB SFU + coturn + jibri → file storage), JWT из messenger, нативный lib-jitsi-meet; 1:1 + групповые конференции + запись по политике. Сигналинг поверх контактов остаётся; детали — в ADR-0062.

  3. Жизненный цикл чата принадлежит CRM (и для лида, и для сделки): состав участников любого CRM-чата — производная от CRM, messenger его не редактирует самостоятельно.
    • Чат лида материализуется уже на стадии лида: messenger создаёт ChatKind.Lead по первому событию лида и синхронизирует участников из pin.crm.lead-participants-changed.v1 (по образцу чата сделки из deal-members-changed). Участники — роли ParticipantRole рабочей группы лида (Executor, Responsible, CoAgent, Assistant, Lawyer, InvestorPrimary/Secondary), маппятся в участников чата; удаление участника из группы лида убирает его из чата.
    • Чат сделки: CRM публикует события (pin.crm.deal-created, deal-stage-changed, document-requested, booking-paid, …); messenger потребляет их — создаёт ChatKind.Deal, синхронизирует участников из deal-members-changed, постит карточки.
    • Переход лид→сделка: при создании сделки из лида CRM выпускает deal-created с ссылкой на исходный лид; messenger связывает существующий ChatKind.Lead с новым ChatKind.Deal (переход к чату сделки), сохраняя историю сообщений лида. Дальнейший состав ведётся уже по deal-members-changed. Отдельный «оркестратор чата и сделок» не создаётся (модуль CRM + события).
  4. Фронт-контракт: REST (история/отправка/чаты/контакты) + SignalR-hub (новые сообщения/статусы); авторизация — JWT Identity. Поле chatStatus на сделке остаётся как проекция готовности чата.

Последствия

  • Полный контроль над моделью данных, видимостью и аудитом; нет операционной зависимости от внешнего чат-сервера.
  • Доставку/масштабирование реализуем сами (SignalR + Redis backplane — готовый блок SDK).
  • Фронт-контракт — REST (история/отправка/чаты/контакты) + подключение к SignalR-hub messenger-сервиса, без внешнего чат-протокола.
  • Консистентность «persist + realtime» — сообщение сначала фиксируется (IUnitOfWork + outbox), затем пушится в hub; консюмеры CRM-событий идемпотентны.
  • Чат материализуется раньше (на стадии лида), что даёт единое пространство переписки рабочей группы до конверсии в сделку; при переходе лид→сделка история не теряется, состав перецепляется на deal-members-changed.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы

  • docs/06-services/messenger/ (детальное ТЗ), docs/06-services/crm/ (события сделки), docs/11-backend-sdk/10-signalr-realtime.md, docs/00-overview/prototype-mapping.md.