ADR-0055: Собственный мини-мессенджер PIN; оркестрация чат↔сделка в CRM-событиях
Статус
Accepted
Дата
2026-07-13
Контекст
ТЗ.md:287–309 требует мессенджер под нужды платформы: личные сообщения, контакты и звонки, чёрный список, чаты сделок с авто-событиями/карточками (целевые действия «загрузите документы», инфо «договор оплачен»), per-participant видимость сообщений, правка/удаление ≤1 минуты с хранимой историей правок, IP/user-agent отправителя. Мессенджер строится с нуля под нужды платформы; фронт ждёт наш контракт. Открытый вопрос ТЗ.md:301 — «модуль в сервисе сделок или микросервис-оркестратор».
Решение
- Пишем собственный мини-мессенджер — контекст
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 — опция при реальном масштабировании. - 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.
- личные чаты 1:1 (по контактам), групповые чаты лидов (
- Жизненный цикл чата принадлежит 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 + события).
- Чат лида материализуется уже на стадии лида:
- Фронт-контракт: REST (история/отправка/чаты/контакты) + SignalR-hub (новые сообщения/статусы);
авторизация — JWT Identity. Поле
chatStatusна сделке остаётся как проекция готовности чата.
Последствия
- Полный контроль над моделью данных, видимостью и аудитом; нет операционной зависимости от внешнего чат-сервера.
- Доставку/масштабирование реализуем сами (SignalR + Redis backplane — готовый блок SDK).
- Фронт-контракт — REST (история/отправка/чаты/контакты) + подключение к SignalR-hub messenger-сервиса, без внешнего чат-протокола.
- Консистентность «persist + realtime» — сообщение сначала фиксируется (
IUnitOfWork+ outbox), затем пушится в hub; консюмеры CRM-событий идемпотентны. - Чат материализуется раньше (на стадии лида), что даёт единое пространство переписки рабочей группы до
конверсии в сделку; при переходе лид→сделка история не теряется, состав перецепляется на
deal-members-changed.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (5): Entity / Aggregate: BlacklistEntity, Entity / Aggregate: ChatEntity (+ParticipantEntity как owned-child), Entity / Aggregate: ChatEventEntity, Entity: CallParticipantEntity (owned-child ConferenceSessionEntity), Entity: ParticipantEntity (owned-child ChatEntity)
- API (39): DELETE /api/agent/crm/deals/{dealId}/members/{memberId} — Удалить участника сделки, DELETE /api/messenger/blacklist/{blockedUserId} — Разблокировать пользователя, DELETE /api/messenger/chats/{chatId}/messages/{messageId} — Удаление сообщения (мягкое, ≤ 1 мин), DELETE /api/messenger/contacts/{contactId} — Удалить контакт / отозвать исходящую заявку, GET /api/messenger/admin/chats/{chatId}/transcript — Выгрузка переписки для арбитража, GET /api/messenger/calls — Журнал звонков (входящие / исходящие / пропущенные), GET /api/messenger/chats — Список чатов пользователя, GET /api/messenger/chats/{chatId} — Карточка чата (участники + read-state), GET /api/messenger/chats/{chatId}/messages — История чата (keyset-пагинация), GET /api/messenger/conferences/{conferenceId} — Карточка конференции, PATCH /api/messenger/chats/{chatId}/messages/{messageId} — Правка сообщения (≤ 1 мин), POST /api/agent/crm/deals — Создать сделку из лида по объекту (+27)
- Use Cases (16): UC-CRM-002. Агент создаёт сделку из лида (выбор объекта, гейт вовлечения застройщика), UC-CRM-003. Движение сделки по канбан-доске, UC-CRM-004. Документы сделки: запрос → загрузка → проверка → статусы, UC-CRM-006. Вмешательство Менеджера ПИН, UC-CRM-007. Добавление участника в сделку, UC-CRM-008. Снятие участника со сделки, UC-CRM-012. Управление составом участников лида (добавление/снятие: второй агент, супруг/юрист инвестора), UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика, UC-FIX-002. Застройщик одобряет фиксацию (авторизация работы с клиентом, без создания сделки), UC-MSG-001. Открыть личный чат по поиску пользователя по телефону, UC-MSG-002. Авто-провизия чата сделки по событию CRM, UC-MSG-003. Правка сообщения в окне ≤ 60 секунд (+4)
- События (10): События сервиса messenger: реестр топиков, Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.deal-created (DealCreatedEvent), Event Contract: pin.crm.deal-members-changed (DealMembersChangedEvent), Event Contract: pin.crm.lead-booking-intent-changed (LeadBookingIntentChangedEvent), Event Contract: pin.crm.lead-participants-changed (LeadParticipantsChangedEvent), Event Contract: pin.messenger.contact-request-accepted (ContactRequestAcceptedEvent), Event Contract: pin.messenger.contact-request-created (ContactRequestCreatedEvent), Event Contract: pin.messenger.deal-chat-status-changed (DealChatStatusChangedEvent), Event Contract: pin.messenger.message-posted (MessagePostedEvent)
- Консюмеры (8): Consumer: Booking Expired Card (MessengerBookingExpiredConsumer), Consumer: Booking Paid Card (MessengerBookingPaidConsumer), Consumer: Deal Chat Members Sync (MessengerDealMembersChangedConsumer), Consumer: Deal Chat Provisioning (MessengerDealCreatedConsumer), Consumer: Deal Chat Status (pin-crm-deal-chat-status), Consumer: Deal Stage Card (MessengerDealStageChangedConsumer), Consumer: Document Request Card (MessengerDocumentRequestedConsumer), Consumer: Lead Chat Participants Sync (MessengerLeadParticipantsChangedConsumer)
- Фоновые задачи (5): Background Job: CallTimeoutReaperJob, Background Job: ChatProvisionRetryJob, Background Job: ConferenceRoomReaperJob, Background Job: RealtimeDispatchJob, Background Job: RecordingFinalizeRetryJob
- Саги (1): Saga «Booking» (Резерв → Бронь → передача в продажу)
- adr (1): ADR-0062: Медиа-стек звонков и запись на Jitsi (JVB SFU + coturn + jibri → file storage; JWT из messenger; нативный lib-jitsi-meet)
- scenarios (1): UC-OVR-006. Сделка: от брони до регистрации (инвестор-инициатор)
- index.md (4): CRM Service, Messenger Service, Notifications Service, Support Service
- calls (2): Жизненный цикл сессии звонка — 1:1 и конференция (messenger), Медиа-звонки и запись на Jitsi — интеграционный гид (messenger)
Связанные документы
docs/06-services/messenger/(детальное ТЗ),docs/06-services/crm/(события сделки),docs/11-backend-sdk/10-signalr-realtime.md,docs/00-overview/prototype-mapping.md.