Перейти к основному содержимому

Event Contract: pin.chessboard.property-object-upserted (PropertyObjectUpsertedKafkaEvent)

Версионируемый контракт интеграционного события «объект недвижимости создан/обновлён». Consumer-документы ссылаются сюда, не переописывая payload. Это «property-object-updated»-событие шахматки: паспортный snapshot объекта недвижимости; смена только статуса и только цены — отдельные специализированные события того же агрегата (property-object-status-changed, property-object-price-changed), чтобы высокочастотные потребители (витрина, аналитика цен) не разбирали полный snapshot.

Назначение

Фиксирует создание PropertyObjectEntity (синк Talent, Developers API) и каждое обновление паспортных полей (Number/Floor/Section/Rooms/площади/LayoutId/WindowViews/Capacity). Событие публичное: даёт downstream-контекстам полный snapshot объекта недвижимости без синхронного запроса в chessboard — карточка и списки объектов витрины, измерение PropertyObject статистики застройщика (ADR-0057), реестр объектов недвижимости для правил вознаграждений и эконом-модели.

Metadata

ПолеЗначение
Event typepin.chessboard.property-object-upserted
C# typePropertyObjectUpsertedKafkaEvent
Topicpin.chessboard.property-object-upserted.v1
Partitions / key6 партиций (объекты недвижимости — самый массовый агрегат шахматки, сотни тысяч) / key = PropertyObjectId — порядок событий по объекту недвижимости гарантирован
ContractVersion1
Producerchessboard: Developers API управления объектами недвижимости, синк Talent (talent-chessboard-sync)
Статусdraft

Событие несёт метаданные: идентификатор сообщения (для дедупликации), время, идентификатор и тип агрегата-источника (AggregateId = PropertyObjectId, AggregateType = "PropertyObject"); сквозной trace восстанавливается из метаданных. Публикуется атомарно со сменой состояния агрегата (транзакционный outbox).

Payload PropertyObjectUpsertedKafkaEvent

Полный паспортный snapshot. Дом полей — entity: PropertyObjectEntity; здесь перечислено только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
PropertyObjectIdGuidДаPropertyObjectEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаPropertyObjectEntity.TenantIdImmutableTenant boundary = DeveloperId (ADR-0052).
ProjectIdGuidДаPropertyObjectEntity.ProjectIdImmutableПроект (ключ иерархии измерений dev-stats).
BuildingIdGuidДаPropertyObjectEntity.BuildingIdImmutableЗдание/литер.
LayoutIdGuid?НетPropertyObjectEntity.LayoutIdAdditive onlyПланировка объекта недвижимости.
LayoutGroupIdGuid?НетLayout.LayoutGroupId (через привязанную планировку; null, если планировки/группы нет)Additive onlyГруппа планировок — денормализация в payload, чтобы dev-stats не джойнил измерения при вставке facts (ADR-0057).
Statusstring (enum PropertyObjectStatus)ДаPropertyObjectEntity.StatusAdditive only (новые значения enum)Free / Reserved / Booked / Sold / Hidden — текущий статус на момент snapshot-а; переходы отслеживаются событием property-object-status-changed.
NumberstringДаPropertyObjectEntity.NumberAdditive onlyНомер объекта (≤ 32, уникален в здании).
FloorshortДаPropertyObjectEntity.FloorAdditive onlyЭтаж (ось Y шахматки).
Sectionshort?НетPropertyObjectEntity.SectionAdditive onlyСекция/подъезд (ось X шахматки).
RoomsshortДаPropertyObjectEntity.RoomsAdditive onlyКомнатность; 0 = студия.
TotalAreadecimalДаPropertyObjectEntity.TotalAreaAdditive onlyОбщая площадь, м².
LivingAreadecimal?НетPropertyObjectEntity.LivingAreaAdditive onlyЖилая площадь, м².
PricedecimalДаPropertyObjectEntity.PriceAdditive onlyТекущая цена на момент snapshot-а; изменения цены отслеживаются событием property-object-price-changed.
CurrencyIdshortДаPropertyObjectEntity.CurrencyIdAdditive onlyВалюта цены — ссылка на справочник валют (= Project.CurrencyId, INV-PRJ-6; RUB-only, ADR-0048).
CurrencySnapshotCurrencySnapshotДареплика справочника на момент публикацииAdditive onlyDisplay-снапшот валюты (IsoCode/Name/Signsnapshots.md); отдельного строкового кода валюты payload не несёт.
PricePerMeterdecimalДаPropertyObjectEntity.PricePerMeterAdditive onlyЦена за м² (вычисляемая денормализация агрегата).
WindowViewsIReadOnlyList<string> (enum WindowViewKind)Да (может быть пустым)PropertyObjectEntity.WindowViews.ViewsAdditive onlyВиды из окон: Sea/City/Courtyard/Mountains/Park/River/Street (reference-data.md); ≤ 4, без дублей.
Capacityshort?НетPropertyObjectEntity.CapacityAdditive onlyВместимость (гостей) — апарт-отели.
VersionlongДаPropertyObjectEntity.VersionImmutable semanticsДоменная версия агрегата; монотонный guard у потребителей (событие с меньшей/равной версией — skip). Version = 1 ⇔ создание объекта недвижимости.
CreatedAtDateTimeOffsetДаPropertyObjectEntity.CreatedAtImmutableМомент публикации/создания объекта недвижимости; неизменен между snapshot-ами. Потребитель dev-stats реплицирует в dim_property_object.PublishedAt для метрики time-to-first-view.

Единственная вложенная модель — CurrencySnapshot: дом полей — snapshots.md, здесь не переописывается; снапшот фиксируется на момент публикации события и не «догоняется». Коллекция WindowViews плоская (enum-строки).

Когда публикуется

УсловиеПереход состоянияСогласованность
Создание объекта недвижимости: синк Talent или Developers API создания объекта недвижимости[*] → Free (или начальный статус синка)публикация атомарна со сменой состояния агрегата
Обновление паспортных полей: Number / Floor / Section / Rooms / TotalArea / LivingArea / LayoutId / WindowViews / Capacity (синк или Developers API)без переходата же
Не публикуется при изменении только Status (→ property-object-status-changed) или только Price (→ property-object-price-changed)

Потребители (consumers)

Consumer-контекстЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
catalogПроекция ApartmentListItem/ApartmentDetail витрины: паспортные поля, привязка планировкиMessageId + guard по Version< 1 мин
dev-statsРеплика измерения PropertyObject (иерархия Developer→Project→Building→LayoutGroup→Layout→PropertyObject для обогащения facts, ADR-0057)то же< 5 мин
rewardsРеестр объектов недвижимости для привязки правил вознаграждений (через LayoutId/LayoutGroupId)то же< 5 мин
invest-analyticsРеестр объектов недвижимости/типов (LayoutId) для эконом-модели и расчёта доходностито же< 5 мин

Доставка и обработка отказов

  • Доставка — at-least-once. Транзакционный outbox гарантирует, что событие не теряется при коммите агрегата, но допускает повторную доставку; порядок сообщений по ключу (PropertyObjectId) сохраняется в пределах партиции.
  • Идемпотентность потребителя. Дубликат отсеивается по идентификатору сообщения; переупорядоченное или устаревшее событие отбрасывается монотонным Version-guard-ом (событие с версией ≤ применённой — skip).
  • Ретраи. Временная ошибка потребителя → повтор с backoff до успеха; событие не теряется, порядок per-object не нарушается.
  • DLQ. Стабильно необрабатываемое сообщение после исчерпания ретраев уходит в DLQ потребителя с алертом — чтобы не блокировать партицию; расхождение реплики закрывается повторной обработкой из DLQ либо ресинком измерения из источника (chessboard).
Порядок между топиками не гарантирован

property-object-upserted / -status-changed / -price-changed — разные топики; относительный порядок между ними не гарантирован. Потребитель, ведущий единую реплику объекта недвижимости, держит общий Version-guard по PropertyObjectId и применяет только события с большей версией (паспортное поле, статус и цена меняют одну доменную версию агрегата).

Окно согласованности

Пока потребитель не догнал (SLA из таблицы «Потребители»), проекция объекта недвижимости витрины и измерения показывают прежний паспортный snapshot; обновление видно с задержкой в пределах SLA (< 1 мин для catalog).

Версионирование и совместимость

  • Версия — один ContractVersion = 1; при несовпадении версии события — warn-and-proceed (потребитель логирует warning и продолжает). Топик кодирует версию конвенцией суффикса .v1.
  • Additive-only для minor: новые поля — optional с безопасным default, без bump.
  • Breaking (удаление/переименование/смена типа/смысла поля) — новый ContractVersion = 2 + новый топик pin.chessboard.property-object-upserted.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum (Status, элементы WindowViews) у потребителя — fallback: warning + сохранение строки как есть; для Status поведение — как для наиболее ограничивающего (Hidden, не показывать).
  • CurrencySnapshot версионируется вместе с несущим контрактом (отдельного ContractVersion нет); изменения snapshot-модели — только additive, неизвестные поля снапшота потребитель игнорирует (snapshots.md).
  • Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.

Безопасность

ПолеКласс данныхПравило
Все идентификаторы, VersionInternalinclude
Status, Number, Floor, Section, Rooms, TotalArea, LivingArea, WindowViews, CapacityPublicinclude
Price, CurrencyId + CurrencySnapshot, PricePerMeterPublic (в публичных проекциях скрывается от неавторизованных потребителем — правило query-проекций property-object.md)include

Событие НЕ содержит secrets, tokens, PII (участники сделок — контекст crm), unbounded вложений (схемы планировок — событие layout-upserted).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы