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

Event Contract: pin.favorites.favorite-added (FavoriteAddedKafkaEvent)

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

Назначение

Фиксирует факт добавления объекта недвижимости в избранное владельцем (авторизованным пользователем или гостевой сессией) и даёт downstream-контекстам это действие без синхронного запроса в favorites: user-activity (модуль actions, ADR-0054) превращает событие в ActionEvent «добавил в избранное» — из истории действий факт попадает в карточку лида CRM (поток «Действия пользователя: user-activitycrm»). Личная заметка (Note) в событие не входит (приватные данные владельца — правило favorite.md).

Metadata

ПолеЗначение
Event typepin.favorites.favorite-added
C# typeFavoriteAddedKafkaEvent (проект Pin.Favorites.Contracts)
Topicpin.favorites.favorite-added.v1
Partitions / key6 партиций (лайк — самое частое действие вовлечения; 1–10k одновременно активных, ADR-0056) / key = AggregateId = FavoriteId (порядок «add → remove» одной записи гарантирован внутри партиции)
ContractVersion1
Producerfavorites: POST /favorites (добавление) и POST /favorites/import (bulk-импорт с устройства)
Owning teamPIN Engagement
Статусdraft

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

Payload FavoriteAddedKafkaEvent

Источники полей — entity: Favorite (раздел «Доменные и интеграционные события»); здесь перечислено только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
FavoriteIdGuidДаFavorite.IdImmutableИдентификатор записи избранного (= AggregateId, = Kafka key).
UserIdGuid?Условно (XOR c AnonymousId, INV-FAV-001)Favorite.UserIdImmutableВладелец-пользователь; null — гостевая запись.
AnonymousIdGuid?Условно (XOR c UserId)Favorite.AnonymousIdImmutableГостевая сессия-владелец (anonymous_id из user-activity); null — запись пользователя. user-activity приписывает действие сессии, а после связывания сессии с пользователем — ему.
PropertyObjectIdGuidДаFavorite.PropertyObjectIdImmutableОбъект недвижимости, добавленный в избранное. Существование объекта недвижимости producer-ом не гарантируется (INV-FAV-004) — consumer не должен на нём падать.
OccurredAtDateTimeOffsetДаFavorite.CreatedAtImmutableМомент добавления (хронология ActionEvent строится по этому полю).

Инвариант payload: заполнен ровно один из UserId / AnonymousId (проекция INV-FAV-001); оба null или оба заполнены — контрактная ошибка producer-а, consumer логирует error и пропускает сообщение (skip, не DLQ — данные невосстановимы ретраем).

Note (заметка ≤ 4000) в payload не входит никогда — приватные данные владельца (favorite.md, раздел «Безопасность»). Вложенных моделей нет.

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

УсловиеПереход состояния
POST /favorites — создана новая запись Favorite (идемпотентный повтор по INV-FAV-002 — запись уже была — событие не публикуется)Favorite: [*] → создана
POST /favorites/import — за каждую фактически созданную запись bulk-импорта (дубли пропущены — событий по ним нет)Favorite: [*] → создана (батч)

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

  • переносе владельца записи в слиянии (AnonymousId → UserId) — факт покрыт pin.favorites.favorites-merged.v1, дублировать перенос N событиями add — лишний трафик и ложные ActionEvent (ADR-0056);
  • установке/изменении заметки (PUT /favorites/{propertyObjectId}/note) — только доменное FavoriteNoteUpdatedEvent (в памяти процесса); заметка приватна и действием для карточки лида не является.

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

Сверено с ../README.md (Events И Consumers) и границами сервисов.

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

CRM не подписан на топик напрямую: карточка лида получает действие через user-activity (консолидация действий — ADR-0054; один consumer вместо веера подписчиков — ADR-0056).

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

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

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

  • Версия — один ContractVersion = 1; при несовпадении версии контракта потребитель логирует warning и продолжает (warn-and-proceed).
  • Additive-only без bump: новые optional-поля с безопасным default.
  • Breaking (удаление/переименование/смена типа UserId/PropertyObjectId) — ContractVersion = 2 + новый топик pin.favorites.favorite-added.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Enum-полей в payload нет — правило unknown enum не применяется.
  • Событие — факт (append-only, не снапшот состояния): потребителю достаточно дедупликации по MessageId, version-guard состояния не нужен.

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

ПолеКласс данныхПравило
FavoriteId, PropertyObjectId, OccurredAtInternalinclude
UserId, AnonymousIdInternal (идентификаторы, не PII-профиль)include
NoteПриватная заметка владельцаexclude — никогда не публикуется

Событие НЕ содержит secrets, tokens, телефонов, ФИО, текста заметок. Topic — внутренний контур Kafka (доступ по ACL сервисов); публичной выдачи события нет.

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

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

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

  • Источник полей и условий: entity: Favorite (INV-FAV-001..006).
  • Парное событие удаления: favorite-removed.md; слияние: favorites-merged.md.
  • Границы/карта API: ../README.md; consumers контекста: ../consumers/README.md.
  • Потребитель (будущий путь): docs/06-services/user-activity/consumers/favorites-actions.md.
  • Решения: ADR-0054 (консолидация действий в user-activity), ADR-0056 (simplicity-first).