Catalog Service (Pin.Catalog)
Назначение
catalog владеет публичным каталогом объектов недвижимости: денормализованными витринными
проекциями проектов и объектов недвижимости, оверрайдами отображения и ранжированием застройщика.
Продвижение (промо-пакеты и покупки) вынесено в отдельный контекст promotion —
catalog выступает консюмером буста (ADR-0060): наполняет денормализованное PromotionBoost
карточек по событию pin.promotion.purchase-changed.v1, но не владеет пакетами/покупками. Предоставляет
ленту /lots и /hotels (бесконечная лента 20–30 карточек, гейт: токен или реферальная cookie),
поиск по названию/адресу/девелоперу (Elasticsearch, ADR-0059), фильтры
(тип/класс/регион/город/статус строительства/цена), сортировки (цена/дата/популярность/окупаемость) и показ
комиссий агентам.
Metadata
| Поле | Значение |
|---|---|
| Статус | draft |
| Owning team | PIN Marketplace |
| Domain | Public Catalog & Merchandising |
| Data classification | Public/Internal (витрина публична; комиссии — internal для агентов; PII нет) |
| Runtime criticality | high (лента и карточки — главный публичный трафик платформы; вход воронки UC-1/2/3) |
Ownership
| Artifact | Owner/Rule |
|---|---|
| PostgreSQL schema | catalog |
| API areas | public (лента/поиск/карточки), developers (ранжирование), admin (оверрайды — m2m, ADR-0053) |
| Owned entities | CatalogProjectCard, CatalogPropertyObjectCard, PropertyObjectDisplayOverride, BuildingDisplayOverride, DeveloperRankingRule; PromotionContribution — лёгкая реплика вкладов продвижения (consumer, consumers/promotion-boost.md) |
| Produced events | none (продвижение владеет контекст promotion; карточки — конечная точка данных, ADR-0056) |
| Consumed events | pin.chessboard.projects.v1, pin.chessboard.buildings.v1, pin.chessboard.property-objects.v1, pin.invest-analytics.property-object-metrics.v1, pin.rewards.commission-rules.v1, pin.user-activity.property-object-view-ended.v1, pin.promotion.purchase-changed.v1 (буст → PromotionBoost) |
| Search indexes | Elasticsearch-проекции catalog-property-objects, catalog-projects (ADR-0059; search/property-objects.md, search/projects.md); PostgreSQL-FTS не используется |
| Analytics facts | none (просмотры — user-activity → dev-stats, ADR-0054/0057) |
Ответственность И Границы
- Делает: денормализованные карточки проектов/объектов недвижимости (лента одной строкой без джойнов),
поиск (Elasticsearch)/фильтры/сортировки, эффективные значения после оверрайдов
(
Effective = Override ?? Source), приоритеты застройщика в рамках его объектов, денормализация комиссии агента изrewardsи буста продвижения изpromotion(PromotionBoost— consumer, ADR-0060), маскирование точных цен/статусов для гостей. - НЕ делает: source of truth структуры недвижимости и шахматки (
chessboard); расчёт метрик доходности/истории цен (invest-analytics); правила комиссий (rewards); продвижение — промо-пакеты и покупки (контекстpromotion, ADR-0060;catalogлишь консюмер буста; оплата отложена — решение №10); заявки на консультацию/бронь — формы каталога отправляются вcrm(service-boundaries, поток «Заявки»); запись просмотров/поисков/фильтров — фронт шлёт события вuser-activity(ADR-0054), статистика застройщика —dev-stats(ADR-0057); избранное/подборки (favorites/selections); сохранение фильтров: URL-параметры (шаринг ссылки) — фронт, «фильтры сессии» —ActionEventвuser-activity. - Source of truth: оверрайды, ранжирование, эффективные display-значения и агрегаты витрины (Price-диапазон/PropertyObjectsAvailable/PopularityScore).
- Реплики/проекции: карточки — реплики
chessboard/invest-analytics/rewards/identity(снапшоты источников — typed jsonbSourceSnapshotс guard-ами монотонности версий);PromotionBoost— проекция активных покупокpromotion(реплика вкладовPromotionContribution); Elasticsearch — rebuildable search projection карточек.
Зависимости
| Зависимость | Тип | Зачем Нужна | Failure mode |
|---|---|---|---|
PostgreSQL (schema catalog) | db | Проекции, оверрайды, ранжирование, продвижение, outbox | Полный отказ каталога |
| Elasticsearch | search projection | Полнотекстовый поиск объектов/проектов (ADR-0059) | Поиск деградирует; лента/карточки по id работают из PostgreSQL |
| Kafka | queue | Консюмеры источников (in), в т.ч. pin.promotion.purchase-changed.v1 (буст) | Витрина работает на последних данных; свежесть деградирует (лаг-алерты) |
chessboard | service (events) | Структура проектов/корпусов/объектов, статусы объектов | Карточки устаревают; новые объекты не появляются |
invest-analytics | service (events) | Метрики: окупаемость, рост, аренда, рейтинг | Метрики устаревают; карточки работают |
rewards | service (events) | Эффективные комиссии для агентов | Комиссии устаревают/не показываются |
promotion | service (events) | Буст продвижения → PromotionBoost карточек (ADR-0060) | Сортировка «Рекомендованные» устаревает; карточки работают |
user-activity | service (events) | property-object-view-ended → популярность | Сортировка «по популярности» устаревает |
identity | service (JWT/permissions) | Авторизация акторов, permission catalog.commissions.view, tenant застройщика | Public-чтение с реф-cookie живёт; персонализированные области недоступны |
| Администратор платформы (admin API) | consumer (m2m, ADR-0053) | Оверрайды | Недоступны admin-правки |
Проекты
src/services/catalog/
Pin.Catalog.Api/ # public + developers + admin API areas
Pin.Catalog.Application/ # обработка операций, пересчёты проекций
Pin.Catalog.Domain/ # агрегаты и проекции domain/*.md
Pin.Catalog.Infrastructure/ # PostgreSQL-хранилище, Elasticsearch-проекции, outbox
Pin.Catalog.Background/ # consumers источников (в т.ч. promotion-boost), jobs: popularity-recompute, delisted-retention, search-rebuild
Pin.Catalog.Migrator/ # миграции схемы
Pin.Catalog.Contracts/ # контракты потребляемых событий (промо-контракт — pin.promotion.*)
Основные Сущности
| Entity | Назначение | Source Of Truth | Документ |
|---|---|---|---|
CatalogProjectCard | Денормализованная карточка проекта: лента, поиск, фильтры, агрегаты цен/объектов | catalog (эффективные значения) / chessboard, invest-analytics (источники) | domain/catalog-project-card.md |
CatalogPropertyObjectCard | Денормализованная карточка объекта недвижимости, комиссия, ранжирование | то же + rewards | domain/catalog-property-object-card.md |
PropertyObjectDisplayOverride / BuildingDisplayOverride | Правки витрины поверх источника | catalog | domain/display-override.md |
DeveloperRankingRule | Приоритет застройщика в рамках своих объектов | catalog | domain/ranking-rule.md |
PromotionContribution | Реплика вкладов активных покупок продвижения → пересчёт PromotionBoost | catalog (consumer; SoT — promotion) | consumers/promotion-boost.md |
ER-обзор
API (карта областей; детали — api/)
- Public (гейт ленты: JWT или реф-cookie
referralUrl; ограниченная карточка проекта — без гейта; маскирование точных цен/статусов для гостя):GET /api/v1/property-objects(лента 20–30, keyset-пагинация, поискsearchTerm, фильтры type/class/region/constructionStatus/price/rooms, сортировки recommended/price/date/popularity/payback — состояние фильтров в URL-параметрах для шаринга),GET /api/v1/property-objects/{id},GET /api/v1/projects(лента/hotelsи главная),GET /api/v1/projects/{id},GET /api/v1/projects/{id}/property-objects(assortment с фильтрами этаж/комнатность/цена),GET /api/v1/catalog/filters(lookup справочников значений фильтров). - Developers (
catalog.ranking.*): CRUDDeveloperRankingRule(list/create/update-priority/enable/disable/delete). Продвижение (покупка пакетов) — контекстpromotion(developers-областьpromotion, ADR-0060). - Admin (m2m, ADR-0053;
catalog.overrides.*): CRUD оверрайдов объектов и корпусов,POST /admin/projections/rebuild(принудительный пересчёт карточек по id — runbook расхождений). CRUD пакетов продвижения — admin-область контекстаpromotion. - Internal: not applicable (потребители читают public API или события).
Events И Consumers
| Type | Name | Topic | Назначение | Link |
|---|---|---|---|---|
| Consumed | PromotionPurchaseChanged | pin.promotion.purchase-changed.v1 | Пересчёт PromotionBoost карточек Project/PropertyObject (буст продвижения, ADR-0060) | consumers/promotion-boost.md |
| Consumed | ProjectUpserted/Deleted | pin.chessboard.projects.v1 | Upsert/делистинг CatalogProjectCard | domain/catalog-project-card.md |
| Consumed | BuildingUpserted/Deleted | pin.chessboard.buildings.v1 | Корпусные поля карточек (DeliveryDate/Readiness/Stars) | domain/catalog-project-card.md |
| Consumed | PropertyObjectUpserted/StatusChanged/Deleted | pin.chessboard.property-objects.v1 | Upsert CatalogPropertyObjectCard, статусы Резерв/Бронь/Продан (поток crm → chessboard → catalog) | domain/catalog-property-object-card.md |
| Consumed | PropertyObjectMetricsUpdated | pin.invest-analytics.property-object-metrics.v1 | Payback/Growth/RentalIncome/Rating/Reliability | domain/catalog-property-object-card.md |
| Consumed | CommissionRulesChanged | pin.rewards.commission-rules.v1 | Per-developer снапшот набора правил → батч-пересчёт диапазона PropertyObjectAgentCommission (PercentMin..PercentMax) объектов застройщика | consumers/rewards-commission.md |
| Consumed | PropertyObjectViewEnded | pin.user-activity.property-object-view-ended.v1 | Инкремент PopularityViews объекта/проекта (ADR-0056 п.3) | domain/catalog-property-object-card.md |
Consumer-документы — в consumers/; имена чужих топиков — конвенция pin.<context>.<event>.v1,
подтверждаются при написании ТЗ контекстов-владельцев.
Runtime И Эксплуатация
| Область | Требование |
|---|---|
| Health checks | /health/live, /health/ready (PostgreSQL, Elasticsearch, Kafka), /health/startup (миграции применены) |
| SLO/SLA | лента p99 < 300 мс, карточка p99 < 200 мс (одна строка + индексы); поиск p99 < 400 мс (Elasticsearch); лаг консюмеров источников < 1 мин (цены/статусы), < 15 мин (метрики/комиссии) |
| Dashboard | catalog-overview (RPS ленты/поиска, latency, лаг консюмеров по топикам, DLQ, freshness search-проекций, размер Delisted) |
| Alerts | лаг pin.chessboard.property-objects.v1 > 5 мин (статусы брони!); лаг pin.promotion.purchase-changed.v1 > 1 мин (буст); DLQ > 0; ошибки пересчёта проекций; расхождение search-проекции с source |
| Runbooks | rebuild проекций по id (admin-API); rebuild/переключение alias search-индексов; replay DLQ консюмеров (в т.ч. promotion-boost); сверка карточек с chessboard (расхождение SourceSnapshot.ChessboardVersion) |
| Feature flags | catalog.promotions.enabled (гейт консюмера буста promotion-boost — не применять PromotionBoost до готовности), catalog.commissions.enabled |
Jobs (Pin.Catalog.Background): popularity-recompute (ночной пересчёт PopularityScore, окно 30 дней),
delisted-retention (очистка Delisted-карточек старше 30 дней), search-rebuild
(bootstrap/восстановление Elasticsearch). Истечение покупок продвижения (expire-promotions) — job
контекста promotion (ADR-0060).
Безопасность
- Actor types: guest (лента с реф-cookie, ограниченные карточки), investor/agent (полные карточки; агент — + комиссии), developer-manager (developers-область в scope компании), администратор платформы (admin API, m2m; admin-область), system (consumers/jobs).
- Permission model:
identity/domain/permission.md—catalog.commissions.view,catalog.ranking.view/manage,catalog.overrides.view/manage(permissions продвиженияpromotion.*— контекстpromotion). - Sensitive data classes: комиссии агентов (internal — только по permission);
Commentоверрайдов (internal); PII в контексте нет. Search-проекции PII не индексируют, security-trimming — до query execution (ADR-0059). - Audit: оверрайды/ранжирование — полный actor-stamping +
Version+ soft-delete; проекции (включаяPromotionBoost/PromotionContribution) — без аудита (trace черезSourceSnapshot/событиеpromotion). - Tenant isolation: ADR-0052 — витрина platform-level (не tenanted);
DeveloperRankingRule— tenanted (TenantId = DeveloperId); гость — маскирование цен/статусов на уровне ответа API.PromotionContribution— не tenanted проекция (карточки платформенные).
Известные Ограничения И Assumptions
- Контракты событий
chessboard/invest-analytics/rewards— assumption (конвенцияpin.<context>.<event>.v1); финализируются в ТЗ этих контекстов (chessboard — Батч 1, invest-analytics/rewards — этот батч). - Контракт порядка событий источников: каталог полагается на партиционирование каждого топика по id
агрегата (проект/корпус/объект) — тогда события одной сущности приходят по порядку и per-entity
консистентность сохраняется. Кросс-топиковый и кросс-сущностный порядок не гарантирован; защита —
guard-ы монотонности версий (событие со
SourceVersion <=уже применённой отбрасывается как устаревшее) + INV-2 «ProjectFirst» (карточка объекта, пришедшая раньше карточки своего проекта, ждёт вHiddenи становится видимой, когда проект появится). Что видит пользователь в промежутке: новый объект недвижимости может некоторое время не показываться в ленте, пока не доедет его проект. - Продвижение вынесено в контекст
promotion(ADR-0060);catalog— консюмер буста (PromotionBoostнаполняется поpin.promotion.purchase-changed.v1). Оплата не реализуется (решение №10) — решается в контекстеpromotion. - Ранжирование действует только в сортировке «Рекомендованные» (принятое решение — domain/ranking-rule.md); прозрачность для явных сортировок.
- Свежесть витрины и search-проекции — eventual, с разными окнами по классу данных: цена/статус
(
chessboard) — цель < 1 мин; метрики доходности (invest-analytics) и комиссии (rewards) — цель < 15 мин; буст продвижения (promotion) — < 1 мин; популярность — лаг закрытия просмотра 10–15 мин (ADR-0056). Что видит пользователь в окне лага: забронированный объект может ещё несколько десятков секунд показываться как доступный, метрика/комиссия — с прежним значением. Это безопасно: критичные для брони проверки статуса объекта выполняетcrmв источнике на шаге бронирования, поэтому устаревший статус в витрине не приводит к переспродаже; каталог — только отображение. PopularityScoreсчитается по завершённым просмотрам (property-object-view-ended, лаг закрытия 10–15 мин — ADR-0056), т.е. популярность отстаёт от реального времени — приемлемо для сортировки.- Каталог — personalization-free (ADR-0056): карточки и публичные методы возвращают одинаковую
платформенную проекцию для всех акторов; per-user поля (просмотры лида, статус избранного, членство в
подборках, заметки) в публичные карточки/методы не добавляются. Обогащение «в контексте инвестора»
собирается вне каталога — в аутентифицированном agent/CRM-слое, пакетно по
idобъектов из ответа каталога (просмотры лида + 3-state избранное «нет/у меня/у инвестора» + подборки), с делегированной авторизацией по активному лиду. Дом обогащения —crm/api/agent/get-property-object-investor-context.md; каталог остаётся источником карточек и не участвует в персонализации.
Связанные Документы
- Домены: catalog-project-card · catalog-property-object-card · display-override · ranking-rule.
- Консюмеры: promotion-boost (буст продвижения) · rewards-commission · chessboard-projection-sync · invest-analytics-enrichment · user-activity-popularity.
- Поиск: search/property-objects.md · search/projects.md.
- Решения: ADR-0052 (tenancy), ADR-0053 (admin), ADR-0054 (просмотры → user-activity),
ADR-0056 (simplicity-first), ADR-0057 (dev-stats), ADR-0059 (Elasticsearch-проекции поиска),
ADR-0060 (продвижение → контекст
promotion); technical-assignment.md (решения №10, №11), service-boundaries.md, glossary.md. - Соседи:
chessboard/·invest-analytics/·rewards/·promotion/(продвижение) ·crm/(заявки из каталога) ·user-activity/·dev-stats/.