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

Event Contract: pin.chessboard.property-object-status-changed (PropertyObjectStatusChangedKafkaEvent)

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

Назначение

Фиксирует каждый переход PropertyObjectEntity.Status (INV-CHS-001) — Свободен → Резерв → Бронь → Продан, обратные release-ы, а также Свободен ↔ Скрыт и Свободен → Продан (продажа вне PIN). Событие публичное: даёт downstream-контекстам актуальный статус объекта недвижимости и моменты истечения холдов без синхронного запроса в chessboard — цветовая индикация ячейки шахматки и доступности объекта недвижимости в витрине, пометка «продан»/«снят» в избранном и подборках, уведомления инвестора об отслеживаемом объекте недвижимости. Источник истины текущего статуса — шахматка (CRM лишь инициирует переходы событиями).

Metadata

ПолеЗначение
Event typepin.chessboard.property-object-status-changed
C# typePropertyObjectStatusChangedKafkaEvent
Topicpin.chessboard.property-object-status-changed.v1
Partitions / key6 партиций (объекты недвижимости — самый массовый агрегат шахматки, сотни тысяч) / key = PropertyObjectId — порядок переходов по объекту недвижимости гарантирован
ContractVersion1
Producerchessboard: consumer crm-property-object-status (Crm), Developers API Hide/Unhide (DeveloperApi), процесс property-object-hold-expiration (ExpirationJob), синк talent-chessboard-sync (TalentSync)
Статусdraft

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

Payload PropertyObjectStatusChangedKafkaEvent

Дельта перехода статуса. Дом полей — entity: PropertyObjectEntity; здесь перечислено только то, что реально в событии. FromStatus/ToStatus берутся из доменного события перехода (From, To, StatusSource).

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
PropertyObjectIdGuidДаPropertyObjectEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаPropertyObjectEntity.TenantIdImmutableTenant boundary = DeveloperId (ADR-0052).
ProjectIdGuidДаPropertyObjectEntity.ProjectIdImmutableПроект — адресация проекции витрины и пересчёта агрегатов.
BuildingIdGuidДаPropertyObjectEntity.BuildingIdImmutableЗдание — адресация ячейки шахматки (building_id, floor, section).
FromStatusstring (enum PropertyObjectStatus)Дастатус до переходаAdditive only (новые значения enum)Статус до перехода: Free/Reserved/Booked/Sold/Hidden.
ToStatusstring (enum PropertyObjectStatus)ДаPropertyObjectEntity.Status (новый)Additive onlyСтатус после перехода; терминальный — Sold.
StatusSourcestring (enum PropertyObjectStatusSource)ДаPropertyObjectEntity.StatusSourceAdditive onlyКто выполнил переход: Crm/TalentSync/DeveloperApi/ExpirationJob (reference-data.md).
ReservedUntilDateTimeOffset?НетPropertyObjectEntity.ReservedUntilAdditive onlyМомент истечения резерва; заполнен ⇔ ToStatus = Reserved (INV-CHS-002), иначе null.
BookedUntilDateTimeOffset?НетPropertyObjectEntity.BookedUntilAdditive onlyМомент истечения брони; заполнен ⇔ ToStatus = Booked, иначе null.
HoldDealIdGuid?НетPropertyObjectEntity.HoldDealIdAdditive onlyId сделки CRM, удерживающей объект недвижимости; заполнен ⇔ ToStatus in (Reserved, Booked), иначе null.
SoldAtDateTimeOffset?НетPropertyObjectEntity.SoldAtAdditive onlyМомент продажи; заполнен ⇔ ToStatus = Sold, иначе null.
StatusChangedAtDateTimeOffsetДаPropertyObjectEntity.StatusChangedAtImmutable semanticsМомент перехода (для таймлайнов и диагностики залипших таймеров).
VersionlongДаPropertyObjectEntity.VersionImmutable semanticsДоменная версия агрегата; монотонный guard у потребителей (событие с меньшей/равной версией — skip).
Payload плоский, без snapshot

Вложенных моделей нет. Паспортные поля (Number/Floor/площади/Price) в событие не входят (дом — property-object-upserted); потребитель, которому нужен snapshot, ведёт локальную реплику по PropertyObjectId из upserted-события.

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

Событие публикуется на каждый переход Status (INV-CHS-001), атомарно со сменой состояния объекта недвижимости.

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

При обновлении только паспортных полей публикуется property-object-upserted; при изменении только цены — property-object-price-changed; если Status не изменился — событие не публикуется.

Условие (источник перехода)Переход состоянияСогласованность
События CRM через consumer crm-property-object-status (StatusSource = Crm)Free → Reserved (Reserve), Reserved → Booked (Book), Reserved|Booked → Free (Release), Booked → Sold (MarkSold)публикация атомарна со сменой состояния агрегата
Developers API управления объектом недвижимости (StatusSource = DeveloperApi)Free → Hidden (Hide, INV-CHS-009), Hidden → Free (Unhide)та же
Страховочный процесс property-object-hold-expiration (StatusSource = ExpirationJob)Reserved|Booked → Free (Release по истечении таймера + grace 10 мин — потерян release CRM)та же
Синк Talent Chessboard (StatusSource = TalentSync)Free → Sold (MarkSold — продажа вне PIN)та же

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

Consumer-контекстЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
catalogИндикация статуса в шахматке (цвет ячейки) и доступности объекта недвижимости в витрине; Hidden/Sold убираются из публичных срезовMessageId + guard по Version< 1 мин
favoritesПометка «продан»/«снят» у объекта недвижимости в избранном инвесторато же< 5 мин
selectionsПометка «продан» у объекта недвижимости в подборках агента/инвесторато же< 5 мин
notificationsУведомление инвестора об изменении статуса отслеживаемого объекта недвижимости (продан / снова свободен)то же< 5 мин

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

  • Доставка — at-least-once. Транзакционный outbox гарантирует, что переход не теряется при коммите смены состояния, но допускает повторную доставку; порядок переходов по ключу (PropertyObjectId) сохраняется в пределах партиции.
  • Идемпотентность потребителя. Дубликат отсеивается по идентификатору сообщения; переупорядоченный или устаревший переход отбрасывается монотонным Version-guard-ом (событие с версией ≤ применённой — skip). Итог: повторная доставка Free → Sold или release не «мигает» статусом у потребителя.
  • Ретраи. Временная ошибка потребителя → повтор с backoff до успеха; событие не теряется, порядок переходов per-object не нарушается.
  • DLQ. Стабильно необрабатываемый переход после исчерпания ретраев уходит в DLQ потребителя с алертом — чтобы не блокировать партицию; расхождение закрывается повторной обработкой из DLQ либо ресинком из источника (chessboard — source of truth статуса).
  • Окно согласованности. Между переходом и обновлением проекции ячейка шахматки/доступность объекта недвижимости у потребителя кратковременно показывают прежний статус (SLA catalog < 1 мин, пометки «продан» в избранном/подборках и уведомления — < 5 мин); source of truth текущего статуса — всегда chessboard.

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

  • Версия — один ContractVersion = 1; при несовпадении версии события — warn-and-proceed. Топик кодирует версию конвенцией суффикса .v1.
  • Additive-only для minor: новые поля — optional с безопасным default, без bump.
  • Breaking (удаление/переименование/смена типа/смысла поля) — новый ContractVersion = 2 + новый топик pin.chessboard.property-object-status-changed.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum (FromStatus/ToStatusPropertyObjectStatus; StatusSource) у потребителя — fallback: warning + сохранение строки как есть; для неизвестного ToStatus вести себя как для наиболее ограничивающего значения Hidden (fail closed — убрать объект недвижимости из публичных срезов).
  • Порядок переходов по объекту недвижимости гарантирован key-ом PropertyObjectId внутри этого топика; относительно property-object-upserted/property-object-price-changed (другие топики) порядок не гарантирован — потребитель обязан держать монотонный Version-guard по PropertyObjectId.
  • Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.

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

ПолеКласс данныхПравило
PropertyObjectId, TenantId, ProjectId, BuildingId, Version, StatusChangedAtInternalinclude
FromStatus, ToStatus, StatusSourcePublic/Internalinclude (статус публичен в шахматке; фильтрация Hidden/Sold для неавторизованных — на стороне query-проекций потребителя)
ReservedUntil, BookedUntil, SoldAtPublicinclude (обратный отсчёт/дата продажи в UI витрины)
HoldDealIdInternalinclude только для доверенных consumer-ов (ACL топика) — id сделки чужого контекста (crm), внутренний ключ идемпотентности; в публичные проекции витрины не выводится

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

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

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

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