Talent Price Sync: сведён к consumer-у chessboard-price-changed (решение, не отдельный consumer)
Документ-решение о канале поступления цен из Talent Chessboard в invest-analytics. Отдельный consumer
«синка цен из Talent» не создаётся: постоянный поток цен уже идёт через событие
pin.chessboard.property-object-price-changed.v1, и его обрабатывает
chessboard-price-changed.md; первичная (разовая) загрузка накопленных цен —
background job, а не consumer. Ниже — обоснование и разграничение двух путей.
Metadata
| Поле | Значение |
|---|---|
| Статус | draft (решение зафиксировано; job первичной загрузки — будущий док) |
| Owning team | Backend PIN (инвест-аналитика) |
| Область действия | Канал поступления цен Talent Chessboard → pin_analytics.property_object_price_history_facts |
| Обоснование | Уточняет требование «цены поступают из Talent Chessboard при обновлении объекта» с учётом границ контекстов (service-boundaries.md: Talent Chessboard → chessboard → invest-analytics) |
Решение
| Путь | Механизм | Документ |
|---|---|---|
| Постоянный поток изменений цен | Consumer chessboard-price-changed на pin.chessboard.property-object-price-changed.v1 (ChangeSource = talent-sync) | chessboard-price-changed.md |
| Первичная загрузка при вводе сервиса | Разовый background job price-history-backfill (ChangeSource = backfill, OldPrice = NULL), чтение текущих цен объектов недвижимости из Talent Chessboard API, checkpoint по диапазону PropertyObjectId в PG | ../jobs/price-history-backfill.md (будущий док) |
Прямого подключения invest-analytics к Talent Chessboard (API-поллинг, CDC, отдельный топик Talent) в
постоянном режиме нет.
Обоснование сведения к одному consumer-у
- Границы контекстов. Интеграция с Talent Chessboard — ответственность контекста
chessboard(docs/00-overview/service-boundaries.md: «Объекты/цены Talent → Talent Chessboard →chessboard,invest-analytics»).chessboardсинхронизирует объекты недвижимости с Talent и на каждое изменениеPrice— в том числе пришедшее синком из Talent — надёжно публикует событиеpin.chessboard.property-object-price-changed.v1(docs/06-services/chessboard/domain/property-object.md). Требование «цены получаем из Talent Chessboard при обновлении объекта» выполняется транзитивно, без второго канала. - Один источник истины на запись. Два независимых пути (событие
chessboard+ прямой синк Talent) писали бы одно изменение цены дважды с разнымиSourceEventId— dedupReplacingMergeTreeпоFactId = SourceEventIdтакие «дубли по смыслу» не схлопнул бы. Один пишущий поток исключает класс ошибок целиком. - Simplicity-first (ADR-0056). Отдельный интеграционный клиент Talent в
invest-analytics— это второй HTTP/CDC-контракт, второй маппинг, второй мониторинг и второй runbook без измеренной необходимости: лаг событийного пути укладывается в freshness SLA факта ≤ 5 минут. - Консистентность измерений. Событие
chessboardнесёт снапшот измерений (DeveloperId/ProjectId/ BuildingId/LayoutGroupId/LayoutId/AreaTotal — зависимость к ТЗchessboard, см. chessboard-price-changed.md); прямой синк из Talent потребовал бы собственного разрешения измерений (реплика справочников либо синхронные запросы) — отвергнуто решением №5 в ../README.md.
Почему первичная загрузка — job, а не consumer
- Топик
pin.chessboard.property-object-price-changed.v1на момент вводаinvest-analyticsне содержит истории «до начала публикации»: стартовые цены существующих объектов недвижимости взять из потока неоткуда — нужна разовая выгрузка (Talent Chessboard API, см. ../domain/price-history-fact.md, «Consumers И Backfill»). - Это конечная, перезапускаемая задача с checkpoint-ом по диапазону
PropertyObjectId(таблица состояния job-а в PostgreSQL), а не бесконечный поток — по классификации это background-job, не consumer. - Разграничение с потоком:
ChangeSource = backfill,OldPrice = NULL,SourceEventId = backfill:{PropertyObjectId}:{ChangedAt:O}— коллизии с GuidEventIdсобытия невозможны; повторный прогон backfill идемпотентен по тому жеFactId-dedup.
Диаграмма каналов
Гарантии и проверяемость решения
Решение держится на четырёх проверяемых инвариантах — их нарушение = сигнал к пересмотру:
- Единственный писатель ленты. В ленту
pin_analytics.property_object_price_history_factsпишут только два процесса одного контекста — потоковый consumer и разовый backfill-job; прямой записи из API, других сервисов или Talent нет. Проверка: источник строки читается по колонкеChangeSource∈ {talent-sync,backfill}, иных значений быть не должно. - Непересекающиеся namespace-ы ключа дедупликации. У потока
SourceEventId = EventIdсобытия (Guid), у backfill —backfill:{PropertyObjectId}:{ChangedAt:O}; пространства не пересекаются, поэтомуFactId-dedupReplacingMergeTreeкорректно схлопывает повторы внутри канала и не «слепляет» строки разных каналов. - Одно изменение цены — один
FactId. Поскольку изменение приходит ровно одним путём (событиеchessboard), одно бизнес-изменение цены не может получить два разныхSourceEventId— класс «дублей по смыслу», которые dedup не схлопнул бы, исключён конструктивно. - Freshness событийного пути. Лаг «изменение в Talent → видимость в API» укладывается в SLA факта ≤ 5 мин (батч consumer ≤ 1000 строк / 5 с). Систематическое нарушение — триггер условия (б) пересмотра.
Пересмотр решения
Отдельный канал синка цен из Talent появится только если: (а) chessboard перестанет быть транзитом цен
(изменение границ — через ADR), либо (б) измеренный лаг событийного пути систематически нарушает freshness
SLA ≤ 5 минут и не лечится настройкой consumer-а. До этого любой запрос «добавить прямой синк» отклоняется
со ссылкой на этот документ.
Обратные ссылки
Автоссылки: где используется этот документ.
- События (2): События invest-analytics: карта контрактов, Event Contract: pin.chessboard.property-object-price-changed (PropertyObjectPriceChangedKafkaEvent)
- Консюмеры (1): Consumer: ChessboardPropertyObjectPriceChangedConsumer
- index.md (1): InvestAnalytics Service
- jobs (1): Background Job: PriceHistoryBackfillJob
- registries (1): Реестр событий PIN
Связанные документы
- Consumer потока: chessboard-price-changed.md; дом факта: ../domain/price-history-fact.md; job первичной загрузки: ../jobs/price-history-backfill.md (будущий док).
- Границы:
docs/00-overview/service-boundaries.md. - ADR-0056 (simplicity-first).