События контекста selections: карта топиков и отклонённые события
Индекс интеграционных событий pin.selections.* + дом решения о непубликуемых событиях
(snapshot-created). Минимальность — ADR-0056: каждый топик существует только при живом потребителе,
которому синхронный API не подходит.
Metadata
| Поле | Значение |
|---|---|
| Owning team | PIN Engagement |
| Статус документа | draft |
| Область действия | контекст selections, папка events/ |
| Assumptions | consumer item-added активны: user-activity — consumers/selections-events.md, dev-stats — consumers/selection-item-added.md; consumer created/updated/deleted (user-activity) — будущий путь docs/06-services/user-activity/consumers/selections-actions.md |
| Change context | greenfield; фиксирует список Produced events из ../README.md |
Публикуемые события (по одному контракт-доку на топик)
| Событие | Topic | Producer-операции | Consumer | Контракт |
|---|---|---|---|---|
SelectionCreatedKafkaEvent | pin.selections.selection-created.v1 | POST /selections, POST /selections/from-favorites | user-activity (ActionEvent → карточка лида CRM) | selection-created.md |
SelectionUpdatedKafkaEvent | pin.selections.selection-updated.v1 | PUT /selections/{id}, PUT /selections/{id}/items | user-activity | selection-updated.md |
SelectionItemAddedKafkaEvent | pin.selections.selection-item-added.v1 | PUT /selections/{id}/items (per новый объект недвижимости в диффе), POST /selections/from-favorites (per скопированный объект недвижимости) | dev-stats (этап Selections воронки), user-activity (AddToSelection) | selection-item-added.md |
SelectionDeletedKafkaEvent | pin.selections.selection-deleted.v1 | DELETE /selections/{id} (soft) | user-activity | selection-deleted.md |
SelectionSharedKafkaEvent | pin.selections.selection-shared.v1 | POST /selections/{id}/share (агент отправил подборку клиенту) | crm (лента лида SelectionAttached), user-activity | selection-shared.md |
Все пять — через транзакционный outbox атомарно с мутацией/операцией агрегата; ключ = SelectionId;
ContractVersion = 1. Порядок между топиками не гарантирован — потребитель упорядочивает по
OccurredAt (в пределах одной подборки поэлементные item-added упорядочивает Version; правило в
каждом контракте). selection-item-added — поэлементное (одно событие на один добавленный объект
недвижимости), а не агрегатное: set-items-батч из N новых объектов даёт N событий (обоснование
per-item vs batch — в контракте, раздел «Батч и гранулярность»).
Доставка, идемпотентность и обработка отказов (общее для всех топиков)
Единая политика для пяти топиков; частные ключи идемпотентности и DLQ-условия — в каждом контракте.
- Доставка — at-least-once через транзакционный outbox: пока мутация агрегата не зафиксирована,
событие потребителям не уходит; после фиксации доставляется гарантированно. Застрявший/отстающий
outbox — alert
outbox-lag > 5 мин(../README.md, Alerts); события не теряются, догоняют после разбора. - Идемпотентность потребителя — дедуп по
MessageId; дополнительно по бизнес-ключу: воронкаdev-stats— поSelectionItemId(uniqState, порядку не чувствительна),crmиuser-activity— поSourceMessageId. Повторная доставка того же события → skip без побочных эффектов. - Порядок — внутри одной подборки гарантирован ключом
SelectionId(все события подборки в одной партиции); между топиками не гарантирован — потребитель упорядочивает поOccurredAt(поэлементныеitem-added— поVersion). Проекции upsert-семантичны и терпятupdated/deleted, пришедшие раньшеcreated. - Транзиентный сбой потребителя → повторная доставка до успешного ack (порядок в партиции
сохраняется). Неустранимая ошибка (напр. неизвестное значение
Actionвselection-shared→CRM_SELECTION_UNKNOWN_ACTION) → non-retriable DLQ + alert; потери нет (топики append-only, реплей возможен). - Отставание потребителя источник не блокирует: подборка и её публичное/internal чтение — синхронный
API (source of truth); события лишь наполняют ленты действий, воронку и ленту лида — согласованность
eventual, окно = SLA соответствующего консюмера (< 1 мин
user-activity, < 5 минdev-stats/crm).
Отклонённые события (решение, не пробел)
pin.selections.selection-shared.v1 — пересмотрено, публикуется
Ранее отклонялось: у «шаринга» не было backend-операции в selections (SharePopover копирует ссылку
на клиенте); клик «поделиться» и просмотры по публичной ссылке — зона user-activity; привязка к
лиду — данные crm. Клауза «Пересмотр» сработала: появилась серверная операция «агент отправляет
подборку клиенту» POST /selections/{id}/share
(UC-SEL-005) с двумя потребителями,
которым синхронный API не подходит (crm — лента лида SelectionAttached по паре agent↔investor,
которую selections не резолвит; user-activity — ActionEvent агента). Событие вынесено в
«Публикуемые события» выше; контракт — selection-shared.md.
Клиентским осталось (событие selection-shared его не покрывает): шаринг публичной ссылки
/collection/{publicCode} через SharePopover (копирование на фронте) и просмотры по ней — трекает
user-activity (ADR-0054); отправка подборки в сделку — отдельная операция crm
send-deal-selection.md (таймлайн сделки, без Kafka).
pin.selections.snapshot-created.v1 — не публикуется
Кандидат: факт снятия иммутабельной копии подборки для блока поста.
| # | Причина отклонения | Детали |
|---|---|---|
| 1 | Единственный потребитель взаимодействует синхронно | news вызывает POST /internal/selections/{id}/snapshots (m2m) и получает SnapshotId в ответе — событие ему ничего не добавит (../domain/selection-snapshot.md, раздел «Доменные и интеграционные события»). |
| 2 | Snapshot — техническая копия, не действие пользователя | В историю действий владельца подборки попадает публикация поста (зона news), а не внутренняя механика копирования; ActionEvent из snapshot-created был бы шумом. |
| 3 | Консистентность держится на синхронном ответе | news фиксирует SnapshotId в typed-jsonb блоке поста в своей транзакции; событийная связка добавила бы окно рассинхрона. |
Пересмотр: второй потребитель snapshot-ов (например, dev-stats для статистики публикаций) —
повод добавить топик additive, как уже зафиксировано в
../domain/selection-snapshot.md.
Consumed-события: нет
Контекст ничего не потребляет из Kafka: состав from-favorites — синхронный internal API favorites;
snapshot-ы — синхронные вызовы news; чтение для CRM — синхронный internal API
(../README.md, Ownership). Кандидаты-консюмеры не рассматривались — входящих
событийных потребностей у домена нет (подборка не реагирует на изменения объектов недвижимости/пользователей:
недоступные объекты недвижимости фильтрует выдача каталога, INV-SEL-008).
Связанные документы
- Контракты: selection-created.md, selection-updated.md, selection-item-added.md, selection-deleted.md, selection-shared.md.
- Потребители
item-added: ../../dev-stats/consumers/selection-item-added.md, ../../user-activity/consumers/selections-events.md; потребительshared: ../../crm/consumers/selection-shared.md. - Домены: ../domain/selection.md, ../domain/selection-snapshot.md.
- Границы/потоки: ../README.md, service-boundaries.md; решения: ADR-0054, ADR-0056.
- Будущие пути:
docs/06-services/user-activity/consumers/selections-actions.md,docs/06-services/news/domain/post.md,docs/06-services/crm/README.md. - Шаблон: event-contract-template.