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 type | pin.selections.selection-shared |
| Topic | pin.selections.selection-shared.v1 |
| Partitions / key | 3 партиции (низкий объём — ADR-0056) / key = AggregateId = SelectionId (все отправки одной подборки — в одной партиции, по порядку) |
| ContractVersion | 1 |
| Producer | selections: POST /selections/{id}/share (по событию на каждую отправку клиенту) |
| Owning team | PIN Engagement |
| Статус | draft |
Событие публикуется атомарно с фиксацией операции share: пока операция не зафиксирована, событие не отправляется потребителям; после фиксации — доставляется гарантированно (at-least-once). Ключ агрегата —
SelectionId, тип агрегата —Selection; идентификатор сообщения уникален на каждую отправку (ключ дедупа потребителя).
Payload SelectionSharedKafkaEvent
Источники полей — entity: Selection (раздел «Доменные и интеграционные
события»). Состав полей и имена выверены по консюмеру crm
(crm/consumers/selection-shared.md, «Входные данные /
payload события») — он и есть несущий контракт. Вложенных моделей нет.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
SelectionId | Guid | Да | Selection.Id | Immutable | Подборка (= AggregateId, = Kafka key). crm → LeadSelectionPayload.SelectionId. |
SelectionTitle | string | Да | Selection.Title | Additive only | Название (1..256) для читаемого текста ленты («отправил подборку „Видовые у моря“»). crm → LeadSelectionPayload.SelectionTitle (длиннее 256 — truncate на консюмере, событие не теряется). |
PropertyObjectsCount | int | Да | Selection.Items.Count на момент отправки | Additive only | Число объектов недвижимости в подборке (0..200, INV-SEL-004). crm → LeadSelectionPayload.PropertyObjectsCount (>= 0). |
Action | string (enum SelectionShareAction: Shared/Opened) | Да | операция share (Shared) | Additive only (новые значения) | Дискриминатор действия. Producer публикует Shared (агент отправил). Opened (клиент открыл) — зарезервированное значение: этот producer его не эмитит (просмотры трекает user-activity, ADR-0054), консюмер crm мапит его в SelectionViewed заранее (forward-compat). |
AgentUserId | Guid | Да | Selection.OwnerUserId (актор share = владелец-агент) | Immutable | Агент-отправитель. crm резолвит по нему Lead.AgentUserId (сторона агента активной пары). |
InvestorUserId | Guid | Да | вход операции share (целевой клиент) | Immutable | Клиент, которому отправлена подборка. crm резолвит по нему Lead.InvestorUserId. |
OccurredAt | DateTimeOffset | Да | момент отправки (share) | Immutable | Время отправки (дублирует время события). crm → LeadEventEntity.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) |
|---|---|---|---|---|
crm | SelectionSharedConsumer (crm/consumers/selection-shared.md) | LeadEventEntity (SelectionAttached) в ленту активного лида пары (agent↔investor); Lead.LastEventAt | MessageId + business SourceMessageId (unique) | < 5 мин |
user-activity | UserActivitySelectionSharedConsumer (будущий док: 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 DLQCRM_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, OccurredAt | Internal | include |
AgentUserId, InvestorUserId | Internal (id, не PII-профиль; в публичные ответы API не попадают; Kafka-контур внутренний) | include |
PublicCode, Description, список PropertyObjectId, Comment элементов | Internal/приватная зона владельца | exclude |
Событие НЕ содержит secrets, tokens, PII-профиля (ФИО/телефон клиента), полного состава подборки.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: Selection (+ child SelectionItem)
- Use Cases (1): UC-SEL-005. Агент отправляет подборку клиенту в лиде/сделке
- События (1): События контекста selections: карта топиков и отклонённые события
- Консюмеры (1): Consumer: Selection Shared (SelectionSharedConsumer)
- registries (1): Реестр событий PIN
- index.md (1): Selections Service (Pin.Selections)
Связанные документы
- Источник полей и операция share: entity:
Selection(INV-SEL-002/009); сценарий — UC-SEL-005. - Несущий консюмер (имена полей/маппинг ленты): crm/consumers/selection-shared.md;
тип события лида — crm/api/agent/list-lead-events.md
(
SelectionAttached/SelectionViewed), дом — crm/domain/lead.md. - Соседние события: selection-created.md, selection-updated.md, selection-item-added.md, selection-deleted.md; карта — README.md.
- Границы: ../README.md; решения: ADR-0052 (tenancy), ADR-0054 (ActionEvent), ADR-0056 (simplicity).