Consumers контекста favorites: решение «consumed-событий нет»
Контекст favorites не потребляет Kafka-событий. Этот документ — дом решения: какие consumer-ы
рассматривались, почему отклонены и при каких условиях решение пересматривается. Решение фиксируется
явно, чтобы отсутствие потребителей не выглядело упущением.
Metadata
| Поле | Значение |
|---|---|
| Owning team | PIN 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 | Событие входа не определяет «текущее» устройство для favorites | UserSignedInKafkaEvent несёт SessionId refresh-сессии identity, а не AnonymousId активности; сопоставление «refresh-сессия → anonymous_id» — внутренность user-activity. X-Anonymous-Id текущей вкладки достоверно известен только фронту в момент запроса. |
| 3 | Гонка с первичным импортом | Порядок обязателен: importFavorites → merge-preview → merge (шаг 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.