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

Consumers контекста favorites: решение «consumed-событий нет»

Контекст favorites не потребляет Kafka-событий. Этот документ — дом решения: какие consumer-ы рассматривались, почему отклонены и при каких условиях решение пересматривается. Решение фиксируется явно, чтобы отсутствие потребителей не выглядело упущением.

Metadata

ПолеЗначение
Owning teamPIN Engagement
Статус документаdraft
Область действияконтекст favorites, папка consumers/
Assumptionsсвязывание «сессия ↔ пользователь» выполняет user-activity по pin.identity.sign-ins.v1 (ADR-0054); контракт internal API связи — будущий док docs/06-services/user-activity/README.md

Рассмотренный кандидат №1: consumer «session-linked» для слияния при входе

Идея: подписаться на событие входа pin.identity.sign-ins.v1 (../../identity/events/user-signed-in.md) либо на будущее событие user-activity «сессия связана с пользователем» и выполнять слияние избранного автоматически, событием, без запроса фронта.

Отклонено. Причины (по силе):

#ПричинаДетали
1Слияние — выбор пользователя, а не системная реакцияТребуется предложить три варианта (DeviceWins / AccountWins / Merge). Consumer не может показать диалог; событийное автослияние молча применяло бы одну стратегию и уничтожило бы прежнее избранное без согласия (для DeviceWins/AccountWins — необратимая потеря, undo нет). Merge-preview + явный POST /favorites/merge — фронт-flow (../domain/merge-strategy.md).
2Событие входа не определяет «текущее» устройство для favoritesUserSignedInKafkaEvent несёт SessionId refresh-сессии identity, а не AnonymousId активности; сопоставление «refresh-сессия → anonymous_id» — внутренность user-activity. X-Anonymous-Id текущей вкладки достоверно известен только фронту в момент запроса.
3Гонка с первичным импортомПорядок обязателен: importFavoritesmerge-previewmerge (шаг 5 триггеров merge-strategy.md). Consumer, сработавший в момент входа, слил бы избранное до импорта с устройства и потерял бы его для preview.
4Минимальность (ADR-0056)Синхронная проверка связи сессии уже нужна операции слияния (INV-FAV-102, internal API user-activity); дублирующий событийный канал — второй источник истины без выигрыша.

Автослияние вырожденных случаев (таблица триггеров merge-strategy.md) при этом сохраняется — но запускается тем же фронт-вызовом merge-preview/merge после входа, а не consumer-ом.

Рассмотренный кандидат №2: consumer pin.user-activity.session-ended.v1 для чистки гостевых записей

Идея: удалять гостевое избранное по факту завершения сессии.

Отклонено: завершение сессии (polling-delta 10–15 мин, ADR-0056 п.3) не означает отказ от гостевого избранного — гость вернётся завтра в том же браузере с тем же AnonymousId и ждёт свои лайки. Retention решается проще и без связности: background job guest-favorites-cleanup (365 дней от UpdatedAt, ../background/guest-favorites-cleanup.md).

Условия пересмотра

  • Появление у user-activity публичного события «anonymous_id связан с UserId» и продуктового решения о слиянии без диалога (например, флаг favorites.merge-dialog.enabled = false навсегда) — тогда добавляется consumer с обязательной защитой от гонки с импортом.
  • Блокировка/удаление пользователя в identity с требованием чистки его данных — появится consumer топика pin.identity.users.v1 (сообщение UserBlocked; сейчас требование отсутствует: блокировка не трогает избранное — favorite.md, раздел «Связи»).

Фактический поток вместо consumer-а

Гарантии, на которые опирается решение «без consumer»

Отсутствие входных consumer-ов не создаёт пробела, потому что нужные гарантии обеспечены иначе:

ГарантияКак обеспечена без consumer
Связь сессии с пользователем проверена перед слияниемСинхронный internal-запрос user-activity GET /internal/sessions/{anonymousId}/link в момент merge-preview/merge (INV-FAV-102) — не событие, а точная проверка на актуальный момент
Порядок «импорт до слияния»Обеспечивает фронт-флоу import → merge-preview → merge; событийный consumer сработал бы в момент входа и слил бы избранное до импорта с устройства (кандидат №1, причина 3)
Выбор стратегии — за пользователемСлияние инициирует явный POST /favorites/merge, а не молчаливая реакция на событие (кандидат №1, причина 1)
Ретеншен гостевых записейBackground job по времени (updated_at), а не consumer session-ended (кандидат №2)

Обратная сторона: favoritesисточник событий add/remove/merge; их потребитель (user-activity) обязан быть идемпотентным (доставка at-least-once) и корректно обрабатывать окно eventual-consistency между действием пользователя и появлением ActionEvent в ленте/карточке лида.

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

  • ../README.md — Ownership (Consumed events: нет), зависимости.
  • ../domain/merge-strategy.md — триггеры, стратегии, INV-FAV-101..105.
  • ../../identity/events/user-signed-in.md — событие входа (потребляет user-activity, не favorites).
  • Будущий путь: docs/06-services/user-activity/README.md (internal API связи сессии).
  • Решения: ADR-0054, ADR-0056.