События invest-analytics: карта контрактов
Индекс-каталог events/. Контекст invest-analytics публикует два интеграционных Kafka-события;
топики по конвенции pin.invest-analytics.<event>.v1. Отдельные не-событийные потоки (история цен,
расчётные серии) по-прежнему читаются через public API — обоснование сохранено ниже.
Produced-события (интеграционные, Kafka)
| Событие | Топик | Когда публикуется | Потребители | Контракт |
|---|---|---|---|---|
PropertyObjectMetricsUpdatedKafkaEvent | pin.invest-analytics.property-object-metrics.v1 | Пересчёт витринных метрик доходности объекта недвижимости (триггеры: изменение цены, правка эконом-модели, плановый пересчёт) | catalog (денормализация метрик карточки/ленты) | property-object-metrics.md |
EconomicModelChangedKafkaEvent | pin.invest-analytics.model-changed.v1 | Правка эконом-модели админом (scope Global / PropertyObjectType) | invest-analytics (self — internal recompute job → переиздание property-object-metrics.v1) | model-changed.md |
Поток замыкается внутри: model-changed (правка модели) → recompute job → property-object-metrics
(снапшот метрик) → catalog (витрина). Так каталог остаётся denormalized-конечной точкой и не знает об
эконом-модели (ADR-0056). Оба события публикуются через транзакционный outbox, атомарно с мутацией
агрегата/фиксацией пересчёта.
Гарантии доставки produced-событий
| Аспект | model-changed.v1 | property-object-metrics.v1 |
|---|---|---|
| Доставка | at-least-once (транзакционный outbox) | at-least-once (транзакционный outbox) |
| Ключ партиционирования / порядок | ChangeScope (правки модели редки) | PropertyObjectId — порядок snapshot-ов по объекту сохранён |
| Идемпотентность подписчика | монотонный guard по (ChangeScope, GroupId, Version) | монотонный guard по AsOf |
| Публикация no-op | не публикуется, если данные не изменились | не публикуется, если snapshot совпал при том же AsOf |
| Исчерпание retry | сообщение в DLQ + алерт; метрики затронутого scope остаются на прежних значениях | сообщение в DLQ + алерт; карточка сохраняет прежние метрики |
Оба события несут snapshot/scope-семантику, а не дельту: повторная доставка или переигрывание топика приводит подписчика к тому же конечному состоянию (перечитать модель по scope / применить более свежий snapshot). Это делает rebuild проекций каталога и replay топика безопасными.
Что НЕ стало событием (осознанно)
| # | Данные | Почему pull (public API), а не push |
|---|---|---|
| 1 | История цен объекта недвижимости | Нужна фронту (график /lots/[id]/analytics) и investor-owner (динамика портфеля) — оба читают public API GET property-objects/{propertyObjectId}/price-history по запросу. Факт «цена изменилась» уже существует у владельца — pin.chessboard.property-object-price-changed.v1; ретрансляция создала бы второй источник того же факта |
| 2 | Расчётные серии (InvestorMonth[]/AnalyticsSeries/AsideMetrics) | Вычисляются на каждый запрос и не персистятся (решение №3 ../README.md) — детального ряда нечего анонсировать. В событие уходит только компактная витринная проекция метрик (property-object-metrics.v1), нужная каталогу для ленты без синхронных запросов (ADR-0056 «одна строка без джойнов») |
| 3 | Эффективные коэффициенты эконом-модели | В model-changed уходит scope инвалидации, а не значения (Internal-данные наружу не раскрываются) — подписчик перечитывает актуальную модель при пересчёте |
Доменные события (in-memory, не Kafka)
Доменные события поднимаются агрегатами PG-модели и обрабатываются при сохранении внутри сервиса —
audit-лог admin-правок; в том же согласованном изменении upsert/delete/replace конфигурации кладёт
outbox-запись model-changed (см. таблицу выше).
| Событие | Агрегат | Когда | → outbox model-changed | Документ агрегата |
|---|---|---|---|---|
EconomicModelConfigUpdatedEvent | EconomicModelConfig | Успешный PUT model-config | Global/Upserted | ../domain/economic-model-config.md |
PropertyObjectTypeConfigUpsertedEvent | PropertyObjectTypeConfig | Создание/правка типа объекта недвижимости | PropertyObjectType/Upserted | ../domain/property-object-type-config.md |
PropertyObjectTypeConfigDeletedEvent | PropertyObjectTypeConfig | Удаление типа объекта недвижимости | PropertyObjectType/Deleted | ../domain/property-object-type-config.md |
PropertyObjectTypeMonthOverridesReplacedEvent | PropertyObjectTypeConfig (child PropertyObjectTypeMonthOverride) | Replace-all помесячных оверрайдов | PropertyObjectType/MonthOverridesReplaced | ../domain/property-object-type-month-override.md |
Consumed-события (для полноты карты)
pin.chessboard.property-object-price-changed.v1— append факта цены в ClickHouse + триггер пересчёта метрик (../consumers/chessboard-price-changed.md); канал синка Talent — ../consumers/talent-price-sync.md.pin.invest-analytics.model-changed.v1(self) — internal recompute job пересчёта метрик по scope-у (model-changed.md).
Правила добавления новых produced-событий
Новое produced-событие вводится (отдельным контрактом в этом каталоге + строка в таблице «Produced» выше и в
../README.md), когда появляется конкретный consumer, которому нужен push — не «на вырост»
(ADR-0056). Пример нереализованного кандидата: уведомление инвестора о росте цены его объекта недвижимости
сверх порога — сначала оценить, не решается ли это на стороне investor-owner поверх public API истории цен.
Связанные документы
- ../README.md (таблица «Events И Consumers»).
- ADR-0056 (simplicity-first); ADR-0058 (rename
Lot*→PropertyObject*).