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

Event Contract: pin.selections.selection-updated (SelectionUpdatedKafkaEvent)

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

Назначение

Фиксирует правку живой подборки владельцем: название/описание (PUT /selections/{id}) или состав и порядок целиком (PUT /selections/{id}/items). Потребитель — user-activity (ActionEvent «изменил подборку», ADR-0054). Событие сознательно одно на обе операции правки (ADR-0056: два топика title-updated/items-updated не дают потребителю ничего — ActionEvent один и тот же); детализация «что именно поменялось» ленте действий не требуется, а snapshot-ы постов правка живой подборки не затрагивает (INV-SEL-001).

Metadata

ПолеЗначение
Event typepin.selections.selection-updated
Topicpin.selections.selection-updated.v1
Partitions / key3 партиции / key = AggregateId = SelectionId (правки одной подборки — в одной партиции, по порядку)
ContractVersion1
Producerselections: PUT /selections/{id} и PUT /selections/{id}/items
Owning teamPIN Engagement
Статусdraft

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

Payload SelectionUpdatedKafkaEvent

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
SelectionIdGuidДаSelection.IdImmutableИзменённая подборка (= AggregateId, = Kafka key).
OwnerUserIdGuidДаSelection.OwnerUserIdImmutableВладелец — субъект ActionEvent (только он может менять, INV-SEL-002).
ItemsCountintДаSelection.Items.Count после правкиAdditive onlyАктуальное число объектов недвижимости (0..200, INV-SEL-004); при правке только названия — текущее без изменений.
OccurredAtDateTimeOffsetДаSelection.UpdatedAt после мутацииImmutableМомент правки (дублирует время события).

Title/Description, дельта состава (какие объекты недвижимости добавлены/убраны), Comment-ы — не входят: потребителю достаточно факта и счётчика, состав читается API по id (selection.md, правило под таблицей событий). Вложенных моделей нет.

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

УсловиеПереход состоянияПубликация
PUT /selections/{id} — название/описание фактически изменились (no-op с теми же значениями события не публикует)Selection: Active → Active (UpdatedAt обновлён)атомарно с правкой подборки
PUT /selections/{id}/items — состав/порядок фактически изменились (дедупликация INV-SEL-003, нормализация INV-SEL-006, лимит INV-SEL-004)Selection: Active → Activeатомарно с заменой состава

Событие не публикуется при:

  • guard-отказах: чужая или удалённая подборка (INV-SEL-002, INV-SEL-009), конфликт оптимистической блокировки;
  • снятии/отвязке snapshot-а (news, m2m) — сама подборка не меняется;
  • soft delete — это отдельное pin.selections.selection-deleted.v1.

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

Consumer-контекстConsumerЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
user-activityUserActivitySelectionUpdatedConsumer (будущий док: docs/06-services/user-activity/consumers/selections-actions.md)ActionEvent «изменил подборку» (ItemsCount) в историю действий OwnerUserIdMessageId (дубль — skip)< 1 мин

crm не подписан: карточка клиента показывает подборку живым чтением GET /internal/selections/{id} — событие ей не нужно. news не подписан: посты защищены иммутабельными snapshot-ами (INV-SEL-001) и правок живой подборки не видят.

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

  • At-least-once через outbox (атомарно с правкой; конфликт оптимистической блокировки откатывает и правку, и событие — «проигравший» перечитывает и повторяет, событие для отката не публикуется).
  • user-activity дедуплицирует по MessageId; правки одной подборки упорядочены ключом SelectionId внутри партиции, между топиками — по OccurredAt. Событие — уведомительный факт: потеря/отставание не рушит консистентность (состав всегда доступен по API).
  • Транзиентный сбой консюмера → повторная доставка до ack; неустранимая → DLQ + alert, без потери. Общая политика — README.md, раздел «Доставка, идемпотентность и обработка отказов».

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

  • ContractVersion = 1; несовпадение версии события — warn-and-proceed.
  • Additive-only без bump (кандидат на additive-расширение — optional-поле ChangeKind Details/Items, если ленте действий понадобится различать правки); breaking — ContractVersion = 2 + топик pin.selections.selection-updated.v2 + dual-publish + sunset .v1.
  • Enum-полей нет.
  • Кросс-топиковый порядок с created/deleted не гарантирован — consumer упорядочивает по OccurredAt (см. selection-created.md).

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

ПолеКласс данныхПравило
SelectionId, ItemsCount, OccurredAtInternalinclude
OwnerUserIdInternalinclude
Title, Description, дельта PropertyObjectId, Comment элементовInternal/зона владельцаexclude

Событие НЕ содержит secrets, tokens, PII-профиля.

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

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

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