UC-OVR-009. Продвижение поста / объекта недвижимости
Назначение
Cross-service сценарий (ADR-0060): застройщик/автор покупает пакет продвижения для полиморфной цели
(Project / PropertyObject / Post), а контексты-потребители пересчитывают денормализованный вклад
продвижения в ранжирование у себя. Оркеструет promotion (покупка/жизненный цикл); catalog бустит
проекты/объекты, news — посты; notifications уведомляет покупателя. Монетизация продвижения —
спроектирована, реализация оплаты отложена.
Metadata
| Поле | Значение |
|---|---|
| Статус | draft |
| Owning team | promotion |
| Primary capability | content-promotion-boost |
| Risk level | medium |
Scope И Участники
| Участник | Роль |
|---|---|
| Менеджер застройщика / Автор | Актор: покупает пакет продвижения для цели |
promotion | Владелец пакетов/покупок; единственный источник факта продвижения; жизненный цикл (актив/истечение/отмена) |
catalog | Consumer: PromotionBoost карточек Project/PropertyObject |
news | Consumer: Post.PromotionBoost для постов |
notifications | Consumer: уведомление покупателю об активации/истечении |
Preconditions
- Пакеты продвижения заведены администратором (UC-PRM-002).
- Цель существует: проект/объект (
chessboard→catalog) или пост (news).
Основной Сценарий
- Покупатель выбирает пакет и цель (
TargetKind=Project/PropertyObject/Post) и покупает продвижение (purchase-promotion.md, UC-PRM-001). promotionсоздаётPromotionPurchase(активна на срок пакета) и публикуетpin.promotion.purchase-changed.v1(promotion-purchase-changed.md).- Consumer фильтрует цели по
TargetKind:catalog(Project/PropertyObject) пересчитываетPromotionBoostкарточек (promotion-boost.md) — суммаBoostWeightактивных покупок влияет на ранжирование выдачи;news(Post) наполняетPost.PromotionBoost(news/consumers/promotion-boost.md).
notificationsуведомляет покупателя об активации.- Истечение/отмена. Фоновый процесс
promotionистекает покупки (UC-PRM-003) → повторныйpurchase-changed(статус);catalog/newsснимают вклад буста.
Sequence
Постусловие
После успешной покупки гарантированно:
- в
promotionсозданPromotionPurchaseв статусеActive(в no-payment режиме — сразу;PaymentStatus = NotRequired), с иммутабельными снапшотамиPackageCode/PackageKind/BoostWeight/цели/срока (StartsAt..EndsAt); - опубликовано
pin.promotion.purchase-changed.v1(Status = Active, key =PurchaseId); - потребители по фильтру
TargetKindприменили вклад буста:catalogпересчиталPromotionBoostкарточекProject/PropertyObject,news—Post.PromotionBoost(< 1 мин); буст входит слагаемым в ранжирование, но не переопределяет явные пользовательские сортировки; - покупатель уведомлён об активации (< 5 мин).
После истечения/отмены: публикуется повторный purchase-changed (Status = Expired/Cancelled);
потребители снимают вклад буста; покупатель уведомлён. Списание средств — вне контракта (эквайринг
отложен).
Альтернативные Сценарии И Отказы
| Ситуация | Поведение |
|---|---|
| Цель удалена/скрыта | Consumer игнорирует буст для несуществующей цели; вклад не применяется |
| Несколько активных покупок на одну цель | Вклад суммируется (BoostWeight активных покупок), Version-guard от переупорядочивания |
Неизвестный TargetKind для потребителя | Fallback: строка сохраняется + warning; чужой TargetKind просто игнорируется (напр. Post для catalog) |
| Отмена продвижения | cancel-promotion → purchase-changed (Cancelled) → снятие вклада у consumers |
| Истечение опередило активацию (переупорядочивание) | Version-guard: событие с версией ≤ применённой игнорируется — «залипший»/преждевременно снятый буст исключён |
Повторная доставка purchase-changed | Идемпотентно по MessageId + Version-guard — вклад не удваивается/не снимается ошибочно |
| Оплата (эквайринг) | Отложена; покупка моделируется (PaymentStatus = NotRequired), списание средств — вне контракта |
Согласованность И Идемпотентность
- Единственный источник факта продвижения —
promotion;catalog/newsдержат лишь денормализованную проекцию (PromotionBoost), которую пересчитывают на каждое событие. Между покупкой и применением буста — окно eventual-consistency (< 1 мин). - Идемпотентность и порядок. Ключ
PurchaseIdдаёт порядок событий одной покупки; потребители дедуплицируют поMessageIdи держат монотонныйVersion-guard (событие с версией ≤ применённой игнорируется). Терминальные статусыExpired/Cancelledпереходов не имеют — число событий на покупку конечно. - Суммирование вклада. Эффективный
PromotionBoostцели = суммаBoostWeightактивных покупок; истечение/отмена уменьшают сумму.BoostWeight— снапшот, правка пакета его не меняет (INV-2). - Жизненный цикл событием, не клиентом. Истечение инициирует фоновый процесс
promotion(now() >= EndsAt), публикуяpurchase-changed; потребители снимают вклад по событию. - Буст — лишь слагаемое ранжирования (защита от накрутки): веса пакетов контролирует администратор, буст не подменяет явные сортировки пользователя.
Acceptance Criteria (Given/When/Then)
- Happy (покупка → буст). Дано: пакет продвижения заведён, цель (
Project/PropertyObject/Post) существует. Когда: покупатель покупает продвижение. Тогда: созданPromotionPurchase(Active), опубликованpurchase-changed, соответствующий потребитель (catalog/news) применилPromotionBoost, покупатель уведомлён. - Истечение. Дано: активная покупка с
EndsAtв прошлом. Когда: срабатывает фоновое истечение. Тогда:purchase-changed(Expired), потребители сняли вклад буста, покупатель уведомлён. - Отмена. Дано: активная покупка. Когда: покупатель/админ отменяет. Тогда:
purchase-changed(Cancelled), вклад снят. - Суммирование. Дано: две активные покупки на одну цель. Когда: обе применены. Тогда:
PromotionBoost= сумма ихBoostWeight. - Переупорядочивание. Дано:
Expiredдоставлен раньше/повторно относительно применённого состояния. Когда: потребитель обрабатывает. Тогда:Version-guard отбрасывает устаревшее событие, буст корректен. - Оплата отложена. Дано: no-payment режим. Когда: покупка создаётся. Тогда:
PaymentStatus = NotRequired, списания средств нет.
Source Of Truth И Данные
| Данные | Владелец | Consistency rule |
|---|---|---|
PromotionPackage / PromotionPurchase | promotion | Единственный источник факта; жизненный цикл событием |
PromotionBoost карточек | catalog | Денормализованная проекция; идемпотентно MessageId + Version-guard |
Post.PromotionBoost | news | То же; фильтр по TargetKind = Post |
Контракты И Зависимости
- API: purchase-promotion.md.
- Events: promotion-purchase-changed.md
(
pin.promotion.purchase-changed.v1). - Consumers: catalog/promotion-boost.md, news/promotion-boost.md.
- UC сервисов: UC-PRM-001, UC-PRM-003.
- ADR: 0060 promotion-context.
Наблюдаемость
- critical metrics: активные покупки по
TargetKind; лаг применения буста consumer'ами; доля истёкших. - trace path:
promotion(покупка/истечение) → шина событий →catalog/news/notifications. - alerts: буст не снят после истечения; рассинхрон
PromotionBoostс фактом вpromotion.
Риски И Guardrails
- Риск: «залипший» буст после истечения. Control: истечение публикует событие;
Version-guard у consumers. - Риск: накрутка ранжирования. Control: буст — лишь слагаемое ранжирования; веса пакетов контролирует администратор.
Обратные ссылки
Автоссылки: где используется этот документ.
- scenarios (1): Кросс-сервисные сценарии PIN (OVR)
- registries (1): Реестр use cases и user stories PIN