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

Event Contract: pin.favorites.favorite-removed (FavoriteRemovedKafkaEvent)

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

Назначение

Фиксирует явное удаление объекта недвижимости из избранного владельцем (пользователем или гостевой сессией). Единственный потребитель — user-activity (модуль actions, ADR-0054): ActionEvent «удалил из избранного» в историю действий и — через поток user-activity → crm — в карточку лида (агенту важно видеть и охлаждение интереса к объекту недвижимости, не только добавления).

Metadata

ПолеЗначение
Event typepin.favorites.favorite-removed
C# typeFavoriteRemovedKafkaEvent (проект Pin.Favorites.Contracts)
Topicpin.favorites.favorite-removed.v1
Partitions / key6 партиций / key = AggregateId = FavoriteId — remove попадает в ту же партицию, что и add той же записи (одинаковый FavoriteId), порядок «add → remove» сохраняется
ContractVersion1
Producerfavorites: DELETE /favorites/{propertyObjectId}
Owning teamPIN Engagement
Статусdraft

Событие публикуется атомарно с физическим удалением записи Favorite (Favorite — без soft delete, см. favorite.md). Ключ дедупликации у потребителя — идентификатор сообщения (MessageId); ключ партиционирования — FavoriteId (AggregateType = "Favorite").

Payload FavoriteRemovedKafkaEvent

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
FavoriteIdGuidДаFavorite.Id (снимок до удаления)ImmutableИдентификатор удалённой записи; совпадает с FavoriteId её события favorite-added — consumer может связать пару add/remove.
UserIdGuid?Условно (XOR c AnonymousId, INV-FAV-001)Favorite.UserIdImmutableВладелец-пользователь; null — гостевая запись.
AnonymousIdGuid?Условно (XOR c UserId)Favorite.AnonymousIdImmutableГостевая сессия-владелец; null — запись пользователя.
PropertyObjectIdGuidДаFavorite.PropertyObjectIdImmutableОбъект недвижимости, удалённый из избранного.
OccurredAtDateTimeOffsetДамомент выполнения удаления (now)ImmutableМомент удаления (запись физически удалена — timestamp-полей у неё больше нет).

Инвариант payload: заполнен ровно один из UserId / AnonymousId (проекция INV-FAV-001); нарушение — error-лог consumer-а + skip. Note удалённой записи в payload не входит (приватные данные; для истории действий текст заметки не нужен). Вложенных моделей нет.

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

УсловиеПереход состояния
DELETE /favorites/{propertyObjectId} — запись владельца найдена (INV-FAV-005) и физически удалена; повторный DELETE несуществующей записи — идемпотентный успех без событияFavorite: существует → удалена (физически)

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

  • удалениях внутри слияния (AccountWins — все S-записи; Merge — S-дубли; DeviceWins — A-записи): итог покрыт одним pin.favorites.favorites-merged.v1 со счётчиками — веер remove-событий создал бы ложные ActionEvent «пользователь удалил» для технической дедупликации (ADR-0056);
  • retention-чистке гостевых записей job-ом guest-favorites-cleanup (365 дней неактивности) — системная уборка не является действием пользователя, истории действий она не нужна; job удаляет батчами без доменных и интеграционных событий.

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

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

CRM напрямую не подписан (см. favorite-added.md — та же схема).

Гарантии доставки, порядок и восстановление

  • Доставка — at-least-once. Публикация атомарна с физическим удалением записи Favorite: удаление произошло → событие гарантированно доставится; изменение откатилось → события нет. Дубликаты доставки дедуплицируются потребителем по MessageId.
  • Порядок — внутри партиции по FavoriteId. favorite-removed попадает в ту же партицию, что и favorite-added той же записи (общий ключ), поэтому у потребителя remove не может опередить add одной записи. Между разными записями порядок не гарантирован — хронология по OccurredAt.
  • Восстановление у потребителя. Транзиентный сбой обработки — retry до успеха; нарушение XOR-инварианта payload (INV-FAV-001) — error-лог + skip (невосстановимо, не в DLQ); poison — в DLQ по политике потребителя. Replay безопасен: ActionEvent-проекция идемпотентна по MessageId.
  • Окно согласованности. Между DELETE /favorites/{propertyObjectId} (пользователь уже видит снятое «сердечко») и ActionEvent «удалил из избранного» в истории/карточке лида — < 1 мин; недоступность шины добавление/удаление не блокирует.

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

  • ContractVersion = 1; несовпадение версии контракта — warn-and-proceed.
  • Additive-only — новые optional-поля без bump; breaking — ContractVersion = 2 + топик pin.favorites.favorite-removed.v2 + dual-publish + sunset .v1.
  • Enum-полей нет — unknown enum не применяется.
  • Событие — факт: дедупликация по MessageId, replay безопасен (ActionEvent-проекция идемпотентна по MessageId).

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

ПолеКласс данныхПравило
FavoriteId, PropertyObjectId, OccurredAtInternalinclude
UserId, AnonymousIdInternalinclude
Note удалённой записиПриватная заметка владельцаexclude — не публикуется

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

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

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

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