Event Contract: pin.selections.selection-deleted (SelectionDeletedKafkaEvent)
Версионируемый контракт интеграционного события «подборка удалена (soft)». Consumer-документы ссылаются
сюда, не переописывая payload. Дом полей-источников — entity: Selection.
Назначение
Фиксирует soft delete подборки владельцем (DELETE /selections/{id} → DeletedAt = now). Потребитель —
user-activity (ActionEvent «удалил подборку», ADR-0054). Событие — уведомительный факт, а не
механизм консистентности: ссылки CRM и snapshot-ы постов остаются разрешимыми благодаря soft delete, а
публичное открытие удалённой подборки API само вернёт 404 (INV-SEL-009) — потребителям не нужно ничего
чистить по этому событию.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.selections.selection-deleted |
| Topic | pin.selections.selection-deleted.v1 |
| Partitions / key | 3 партиции / key = AggregateId = SelectionId |
| ContractVersion | 1 |
| Producer | selections: DELETE /selections/{id} |
| Owning team | PIN Engagement |
| Статус | draft |
Событие публикуется атомарно с простановкой
DeletedAt(переход подборки в статусDeleted): пока изменение не зафиксировано, событие не отправляется потребителям; после фиксации — доставляется гарантированно (at-least-once). Ключ агрегата —SelectionId, тип агрегата —Selection.
Payload SelectionDeletedKafkaEvent
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
SelectionId | Guid | Да | Selection.Id | Immutable | Удалённая подборка (= AggregateId, = Kafka key). |
OwnerUserId | Guid | Да | Selection.OwnerUserId | Immutable | Владелец — субъект ActionEvent (удалить может только он, INV-SEL-002). |
OccurredAt | DateTimeOffset | Да | Selection.DeletedAt | Immutable | Момент soft delete (дублирует время события). |
Title и состав в payload не входят: ActionEvent ленты действий ссылается на SelectionId
(отображение названия удалённой подборки истории не требуется). Вложенных моделей нет.
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
DELETE /selections/{id} — подборка владельца найдена и была активна; повторный DELETE уже удалённой — идемпотентный успех без события (INV-SEL-009: удалённая не переходит никуда) | Selection: Active → Deleted (DeletedAt = now, строки и items сохраняются) | атомарно с простановкой DeletedAt |
Событие не публикуется при отвязке snapshot-ов (Detach, зона
selection-snapshot.md) и не существует события «подборка
восстановлена» — восстановление из soft delete предусмотрено только runbook-ом вручную
(../README.md, Runbooks); при появлении пользовательского restore событие
добавится additive (pin.selections.selection-restored.v1).
Потребители (consumers)
| Consumer-контекст | Consumer | Эффект (событие → что меняется) | Идемпотентность | Задержка (SLA) |
|---|---|---|---|---|
user-activity | UserActivitySelectionDeletedConsumer (будущий док: docs/06-services/user-activity/consumers/selections-actions.md) | ActionEvent «удалил подборку» в историю действий OwnerUserId | MessageId (дубль — skip) | < 1 мин |
crm на топик не подписан (осознанно): CRM при открытии удалённой подборки получает 404 от
GET /internal/selections/{id} и показывает «подборка удалена» — событие не обязано доходить,
консистентность держится на API (selection.md, таблица событий).
news не подписан: посты живут на snapshot-ах, которые soft delete подборки не трогает
(FK restrict, INV-SNP-006).
Доставка и обработка отказов
- At-least-once через outbox (атомарно с простановкой
DeletedAt); повторныйDELETEуже удалённой события не даёт (INV-SEL-009) — дубли на источнике исключены до Kafka. - Событие — уведомительный факт, а не механизм консистентности: даже полная потеря/DLQ события
безопасна — публичное открытие удалённой подборки API само вернёт 404 (INV-SEL-009), ссылки CRM и
snapshot-ы остаются разрешимыми (soft delete).
user-activityдедуплицирует поMessageId. - Транзиентный сбой консюмера → повторная доставка до ack; неустранимая → DLQ + alert. Общая политика — README.md, раздел «Доставка, идемпотентность и обработка отказов».
Версионирование и совместимость
ContractVersion = 1; несовпадение версии события — warn-and-proceed.- Additive-only без bump; breaking —
ContractVersion = 2+ топикpin.selections.selection-deleted.v2+ dual-publish + sunset.v1. - Enum-полей нет.
- Кросс-топиковый порядок с
created/updatedне гарантирован — consumer упорядочивает поOccurredAt;deleted, пришедший раньшеcreated, — терпимый кейс (проекция upsert-ом).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
SelectionId, OccurredAt | Internal | include |
OwnerUserId | Internal | include |
Title, Description, состав, PublicCode | Internal/зона владельца | exclude |
Событие НЕ содержит secrets, tokens, PII-профиля.
Обратные ссылки
Автоссылки: где используется этот документ.
- События (5): События контекста selections: карта топиков и отклонённые события, Event Contract: pin.selections.selection-created (SelectionCreatedKafkaEvent), Event Contract: pin.selections.selection-item-added (SelectionItemAddedKafkaEvent), Event Contract: pin.selections.selection-shared (SelectionSharedKafkaEvent), Event Contract: pin.selections.selection-updated (SelectionUpdatedKafkaEvent)
- registries (1): Реестр событий PIN
- index.md (1): Selections Service (Pin.Selections)
Связанные документы
- Источник полей: entity:
Selection(INV-SEL-002, INV-SEL-009). - Snapshot-ы переживают удаление: ../domain/selection-snapshot.md (INV-SNP-006).
- Соседние события: selection-created.md, selection-updated.md; карта — README.md.
- Границы: ../README.md; решения: ADR-0054, ADR-0056.