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

Event Contract: pin.selections.selection-shared (SelectionSharedKafkaEvent)

Версионируемый контракт интеграционного события «агент отправил подборку клиенту». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей-источников — entity: Selection.

Назначение

Фиксирует серверную операцию «агент отправляет свою подборку клиенту в лиде» — POST /selections/{id}/share (UC-SEL-005). Двум потребителям синхронный API не подходит: crm дописывает ленту лида (SelectionAttached) по паре agent↔investor, которую selections не резолвит, — консюмер crm/consumers/selection-shared.md; user-activity пишет ActionEvent «отправил подборку» в историю действий агента (ADR-0054). Состав подборки (PropertyObjectId-список) и комментарии в событие не входят — потребителю достаточно факта, названия и счётчика; состав читается internal API по SelectionId.

Metadata

ПолеЗначение
Event typepin.selections.selection-shared
Topicpin.selections.selection-shared.v1
Partitions / key3 партиции (низкий объём — ADR-0056) / key = AggregateId = SelectionId (все отправки одной подборки — в одной партиции, по порядку)
ContractVersion1
Producerselections: POST /selections/{id}/share (по событию на каждую отправку клиенту)
Owning teamPIN Engagement
Статусdraft

Событие публикуется атомарно с фиксацией операции share: пока операция не зафиксирована, событие не отправляется потребителям; после фиксации — доставляется гарантированно (at-least-once). Ключ агрегата — SelectionId, тип агрегата — Selection; идентификатор сообщения уникален на каждую отправку (ключ дедупа потребителя).

Payload SelectionSharedKafkaEvent

Источники полей — entity: Selection (раздел «Доменные и интеграционные события»). Состав полей и имена выверены по консюмеру crm (crm/consumers/selection-shared.md, «Входные данные / payload события») — он и есть несущий контракт. Вложенных моделей нет.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
SelectionIdGuidДаSelection.IdImmutableПодборка (= AggregateId, = Kafka key). crmLeadSelectionPayload.SelectionId.
SelectionTitlestringДаSelection.TitleAdditive onlyНазвание (1..256) для читаемого текста ленты («отправил подборку „Видовые у моря“»). crmLeadSelectionPayload.SelectionTitle (длиннее 256 — truncate на консюмере, событие не теряется).
PropertyObjectsCountintДаSelection.Items.Count на момент отправкиAdditive onlyЧисло объектов недвижимости в подборке (0..200, INV-SEL-004). crmLeadSelectionPayload.PropertyObjectsCount (>= 0).
Actionstring (enum SelectionShareAction: Shared/Opened)Даоперация share (Shared)Additive only (новые значения)Дискриминатор действия. Producer публикует Shared (агент отправил). Opened (клиент открыл) — зарезервированное значение: этот producer его не эмитит (просмотры трекает user-activity, ADR-0054), консюмер crm мапит его в SelectionViewed заранее (forward-compat).
AgentUserIdGuidДаSelection.OwnerUserId (актор share = владелец-агент)ImmutableАгент-отправитель. crm резолвит по нему Lead.AgentUserId (сторона агента активной пары).
InvestorUserIdGuidДавход операции share (целевой клиент)ImmutableКлиент, которому отправлена подборка. crm резолвит по нему Lead.InvestorUserId.
OccurredAtDateTimeOffsetДамомент отправки (share)ImmutableВремя отправки (дублирует время события). crmLeadEventEntity.OccurredAt, обновляет Lead.LastEventAt.

PublicCode, Description, полный список PropertyObjectId, Comment-ы элементов в payload не входят (объём/приватность — правило selection.md; симметрично selection-created.md, исключающему PublicCode): для карточки лида CRM хранит SelectionId и добирает состав синхронно GET /internal/selections/{id}. Отдельного payload-поля Version (в отличие от selection-item-added.md) нет — у отправки нет монотонного счётчика на агрегате; порядок отправок одной подборки консюмер держит по OccurredAt.

Когда публикуется

УсловиеПереход состоянияПубликация
POST /selections/{id}/share — владелец-агент отправил активную подборку клиенту (INV-SEL-002 owner-only, INV-SEL-009 не удалена)Selection: Active → Active (состав не меняется)атомарно с фиксацией операции share

Событие не публикуется при: guard-отказах (чужая/удалённая подборка INV-SEL-002/009); публичном открытии по коду (get-selection-by-code.md — read-only, без Kafka; шаринг публичной ссылки — клиентское действие, трекает user-activity); отправке подборки в сделку (это отдельная операция crm send-deal-selection.md → таймлайн сделки SelectionSent, своего Kafka-события не имеет); правке/создании/удалении подборки (свои топики).

Потребители (consumers)

Сверено с ../README.md (Events И Consumers), crm/README.md (Consumed events) и service-boundaries.md.

Consumer-контекстConsumerЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
crmSelectionSharedConsumer (crm/consumers/selection-shared.md)LeadEventEntity (SelectionAttached) в ленту активного лида пары (agent↔investor); Lead.LastEventAtMessageId + business SourceMessageId (unique)< 5 мин
user-activityUserActivitySelectionSharedConsumer (будущий док: docs/06-services/user-activity/consumers/selections-actions.md)ActionEvent «отправил подборку» в историю действий AgentUserId (ADR-0054)MessageId< 1 мин

dev-stats и news на топик не подписаны: воронку наполняет selection-item-added; news работает только со snapshot-ами.

Доставка и обработка отказов

  • At-least-once через outbox (атомарно с фиксацией операции share). crm и user-activity дедуплицируют по MessageId (+ SourceMessageId у crm) — повторная доставка не плодит записей в ленте лида. Повторная отправка той же подборки тому же клиенту — осознанно новый факт (новый MessageId, новый OccurredAt): консюмер её не схлопывает, это разные действия во времени.
  • Транзиентный сбой консюмера → повторная доставка до ack (порядок отправок подборки держится ключом SelectionId в партиции, консюмером — по OccurredAt). Неустранимая ошибка — неизвестное значение Action → non-retriable DLQ CRM_SELECTION_UNKNOWN_ACTION + alert; известное Opened уже замаплено (forward-compat), потери нет.
  • Если crm не находит активного лида пары agent↔investor — разрешение (создать/подвесить) на стороне консюмера (crm/consumers/selection-shared.md); selections пару не резолвит. Общая политика — README.md, раздел «Доставка, идемпотентность и обработка отказов».

Версионирование и совместимость

  • ContractVersion = 1; несовпадение версии события — warn-and-proceed.
  • Additive-only: новые optional-поля без bump; breaking (удаление/смена смысла) — ContractVersion = 2
    • топик pin.selections.selection-shared.v2 + dual-publish + sunset .v1.
  • Enum SelectionShareAction — unknown value обрабатывается явно: консюмер crm мапит Shared/Opened, неизвестное → non-retriable DLQ (CRM_SELECTION_UNKNOWN_ACTION). Введение фактической публикации Opened (если просмотры зашитого-в-лид клиента понадобится вести из selections) — additive, без bump: значение уже в контракте и в маппинге консюмера.
  • Кросс-топиковый порядок с created/updated/item-added/deleted не гарантирован — консюмер упорядочивает по OccurredAt; лента лида append-only и порядку не чувствительна.

Безопасность

ПолеКласс данныхПравило
SelectionId, SelectionTitle, PropertyObjectsCount, Action, OccurredAtInternalinclude
AgentUserId, InvestorUserIdInternal (id, не PII-профиль; в публичные ответы API не попадают; Kafka-контур внутренний)include
PublicCode, Description, список PropertyObjectId, Comment элементовInternal/приватная зона владельцаexclude

Событие НЕ содержит secrets, tokens, PII-профиля (ФИО/телефон клиента), полного состава подборки.

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

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

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