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

Event Contract: pin.favorites.favorites-merged (FavoritesMergedKafkaEvent)

Версионируемый контракт интеграционного события «избранное сессии слито в аккаунт». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей-источников и алгоритма — merge-strategy.md (сущность FavoritesMergeOperation, стратегии DeviceWins / AccountWins / Merge).

Назначение

Фиксирует завершение операции слияния гостевого и аккаунтного избранного (POST /favorites/merge или автослияние вырожденного случая) одним событием со стратегией и счётчиками — вместо веера favorite-added/favorite-removed по каждой затронутой записи (те при слиянии сознательно не публикуются, ADR-0056). Потребитель user-activity пишет ActionEvent «объединил избранное» в историю действий пользователя.

Metadata

ПолеЗначение
Event typepin.favorites.favorites-merged
C# typeFavoritesMergedKafkaEvent (проект Pin.Favorites.Contracts)
Topicpin.favorites.favorites-merged.v1
Partitions / key3 партиции (низкий объём: ≤ 1 операция на пару «пользователь × сессия», INV-FAV-101) / key = UserId (все слияния одного пользователя — в одной партиции, по порядку)
ContractVersion1
Producerfavorites: операция слияния (шаг 6 алгоритма merge-strategy.md)
Owning teamPIN Engagement
Статусdraft

Событие публикуется атомарно с мутациями записей (удаление / перенос владельца) и созданием записи FavoritesMergeOperation — в одном согласованном изменении (INV-FAV-104). Идемпотентный повтор merge (INV-FAV-101) события не публикует. Ключ дедупликации — идентификатор сообщения (MessageId); ключ партиционирования — UserId; AggregateId = MergeOperationId, AggregateType = "FavoritesMergeOperation".

Payload FavoritesMergedKafkaEvent

Payload — все поля FavoritesMergeOperation (merge-strategy.md), кроме заметок (текст заметок в операции и так не хранится).

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
MergeOperationIdGuidДаFavoritesMergeOperation.IdImmutableИдентификатор операции слияния (= AggregateId).
UserIdGuidДаFavoritesMergeOperation.UserIdImmutableПользователь, в чей аккаунт слито избранное (= Kafka key). ActionEvent приписывается ему.
AnonymousIdGuidДаFavoritesMergeOperation.AnonymousIdImmutableГостевая сессия-источник (связана с UserId — проверено INV-FAV-102).
Strategystring (enum FavoritesMergeStrategy)ДаFavoritesMergeOperation.StrategyAdditive only (новые значения)DeviceWins / AccountWins / Merge — применённая стратегия (текстовая сериализация enum).
IsAutomaticboolДаFavoritesMergeOperation.IsAutomaticAdditive onlytrue — автослияние вырожденного случая без диалога (таблица триггеров merge-strategy.md).
AccountCountBeforeintДаFavoritesMergeOperation.AccountCountBeforeAdditive onlyЗаписей в аккаунте до слияния.
SessionCountBeforeintДаFavoritesMergeOperation.SessionCountBeforeAdditive onlyЗаписей в гостевой сессии до слияния.
MovedCountintДаFavoritesMergeOperation.MovedCountAdditive onlyПеренесено записей сессии в аккаунт.
RemovedFromAccountCountintДаFavoritesMergeOperation.RemovedFromAccountCountAdditive onlyУдалено записей аккаунта (только DeviceWins).
RemovedFromSessionCountintДаFavoritesMergeOperation.RemovedFromSessionCountAdditive onlyУдалено записей сессии (дубли Merge; всё — AccountWins).
NotesMergedCountintДаFavoritesMergeOperation.NotesMergedCountAdditive onlyЗаметок перенесено/перезаписано по таблице конфликтов заметок.
TotalCountAfterintДаFavoritesMergeOperation.TotalCountAfterAdditive onlyИтоговое число записей аккаунта.
OccurredAtDateTimeOffsetДаFavoritesMergeOperation.CreatedAtImmutableМомент выполнения слияния.

Списки PropertyObjectId затронутых записей и тексты заметок в payload не входят (объём + приватность; потребителю достаточно счётчиков — актуальный список читается GET /favorites). Вложенных моделей нет.

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

УсловиеПереход состояния
POST /favorites/merge { strategy } — первое слияние пары (UserId, AnonymousId); мутации записей выполненыFavoritesMergeOperation: [*] → создана; записи Favorite: перенос владельца / физическое удаление по таблице стратегий
Автослияние вырожденного случая (IsAutomatic = true, всегда Strategy = Merge)та же
Повторный merge той же пары (INV-FAV-101)нет перехода — идемпотентный успех, событие не публикуется

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

Consumer-контекстЭффект (side effects)ИдемпотентностьЗадержка (SLA)
user-activity (будущий док: docs/06-services/user-activity/consumers/favorites-actions.md)ActionEvent «объединил избранное» (Strategy, счётчики) в историю действий UserIdMessageId; страховка — business key MergeOperationId (unique по паре, INV-FAV-101)< 1 мин

Карточке лида CRM событие напрямую не нужно: агент видит суммарное действие через историю user-activity, актуальный список избранного — через факты add (карточка лида не пересчитывает состав по merge-счётчикам).

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

  • Доставка — at-least-once. Публикация атомарна с мутациями записей и созданием FavoritesMergeOperation (одно согласованное изменение, INV-FAV-104): операция зафиксирована → событие гарантированно доставится; идемпотентный повтор merge (INV-FAV-101) события не порождает. Дубликаты доставки дедуплицируются по MessageId; страховка — business key MergeOperationId (unique по паре).
  • Порядок — внутри партиции по UserId. Все слияния одного пользователя идут по порядку; на пару «пользователь × сессия» приходится ≤ 1 операция (INV-FAV-101), поэтому конкурирующих merged-событий по одной паре не бывает.
  • Восстановление у потребителя. Транзиентный сбой обработки — retry до успеха; неизвестное значение Strategy (появилась новая стратегия) — не падение: ActionEvent пишется с исходной строкой стратегии + warning; poison — в DLQ по политике потребителя. Replay безопасен (дедупликация MessageId / MergeOperationId).
  • Окно согласованности. Итоговый набор аккаунта доступен сразу после ответа POST /favorites/merge (GET /favorites — то же хранилище, сильная согласованность); ActionEvent «объединил избранное» в истории user-activity появляется асинхронно (< 1 мин).

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

  • ContractVersion = 1; несовпадение версии контракта — warn-and-proceed.
  • Additive-only: новые optional-поля/счётчики — без bump; breaking — ContractVersion = 2 + топик pin.favorites.favorites-merged.v2 + dual-publish + sunset .v1.
  • Unknown enum Strategy (появилась новая стратегия) — fallback: consumer пишет ActionEvent с исходной строкой стратегии + warning; падать нельзя.
  • Событие — факт завершённой операции: replay безопасен (дедупликация MessageId/MergeOperationId).

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

ПолеКласс данныхПравило
MergeOperationId, Strategy, IsAutomatic, счётчики, OccurredAtInternalinclude
UserId, AnonymousIdInternalinclude
Списки PropertyObjectId сторон, тексты заметокПриватные данные владельцаexclude — не публикуются

Событие НЕ содержит secrets, tokens, PII-профиля. AnonymousId в событии — уже связанная с пользователем сессия (INV-FAV-102), утечки чужого идентификатора нет.

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

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

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