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

Event Contract: pin.chessboard.building-upserted (BuildingUpsertedKafkaEvent)

Версионируемый контракт интеграционного события «здание создано/обновлено». Consumer-документы ссылаются сюда, не переописывая payload. Это snapshot-событие агрегата «Здание» (литер/корпус): создание и значимое изменение публичных полей здания. Смена только денормализованных счётчиков объектов недвижимости (PropertyObjectsTotalCount/PropertyObjectsFreeCount — вне payload) событие не порождает.

Назначение

Фиксирует создание BuildingEntity (синк Talent, Developers API) и каждое значимое обновление публичных полей здания (Name/Status/FloorsTotal/SectionsCount/Delivery*/ConstructionStatus/ ReadinessPercent). Событие публичное: даёт downstream-контекстам snapshot здания без синхронного запроса в chessboard — карточка проекта и фильтр «срок сдачи» витрины (catalog), измерение Building в статистике застройщика (dev-stats, ADR-0057).

Metadata

ПолеЗначение
Event typepin.chessboard.building-upserted
C# typeBuildingUpsertedKafkaEvent
Topicpin.chessboard.building-upserted.v1
Partitions / key3 партиции (зданий — десятки тысяч, низкий объём) / key = Id — порядок per-building гарантирован
ContractVersion1
Producerchessboard: Developers API create-building / update-building / set-building-status, синк Talent (talent-chessboard-sync), job project-aggregates-recalc (только при мутации snapshot-поля — см. «Когда публикуется»)
Статусdraft

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

Payload BuildingUpsertedKafkaEvent

Snapshot публичного бизнес-состояния здания. Дом полей — entity: BuildingEntity; здесь перечислено только то, что реально в событии. Реплика-поля/PIN-поля (SyncSource), денормализованные счётчики, аудит и concurrency в payload не входят — потребителям нужен публичный срез, а не внутреннее устройство агрегата.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
IdGuidДаBuildingEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаBuildingEntity.TenantIdImmutableTenant boundary = DeveloperId (ADR-0052).
ProjectIdGuidДаBuildingEntity.ProjectIdImmutableПроект-родитель (ключ иерархии измерений dev-stats).
Statusstring (enum BuildingStatus)ДаBuildingEntity.StatusAdditive only (новые значения enum)Draft / Published / Hidden (building.md, stateDiagram).
NamestringДаBuildingEntity.NameAdditive onlyНазвание литера/корпуса («Литер 3», «Корпус Б»); ≤ 128, уникально в проекте (INV-BLD-2).
FloorsTotalshortДаBuildingEntity.FloorsTotalAdditive onlyЭтажность — ось Y шахматки; 1..200.
SectionsCountshort?НетBuildingEntity.SectionsCountAdditive onlyЧисло секций/подъездов — ось X шахматки; 1..50.
DeliveryYearshort?НетBuildingEntity.DeliveryYearAdditive onlyГод сдачи; 2000..2100. Основа фильтра «срок сдачи» витрины.
DeliveryQuartershort?НетBuildingEntity.DeliveryQuarterAdditive onlyКвартал сдачи; 1..4, обязателен при заполненном DeliveryYear; отображение «IV кв. 2027».
ConstructionStatusstring (enum ConstructionStatus)ДаBuildingEntity.ConstructionStatusAdditive only (новые значения enum)Planned / UnderConstruction / Commissioned / Delivered (reference-data.md); может отличаться от статуса проекта (очереди).
ReadinessPercentdecimalДаBuildingEntity.ReadinessPercentAdditive onlyГотовность здания, 0..100, precision (5,2).
VersionlongДаBuildingEntity.VersionImmutable semanticsДоменная версия агрегата; монотонный guard у потребителей (событие с меньшей/равной версией — skip). Version = 1 ⇔ создание здания.

Вложенных моделей нет — все поля здания атомарны. DeliveryQuarter != null ⇒ DeliveryYear != null — потребитель может полагаться на парность при построении фильтра «срок сдачи».

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

Значимое изменение — мутация любого snapshot-поля бизнес-состояния. Изменение только денормализованных счётчиков объектов недвижимости (PropertyObjectsTotalCount/PropertyObjectsFreeCount), аудита или ReplicaVersionне значимое, событие не порождает.

УсловиеПереход состоянияСогласованность
Создание здания: синк Talent или Developers API создания здания[*] → Draft (или начальный статус синка)публикация атомарна со сменой состояния агрегата
Значимое обновление: Name / FloorsTotal / SectionsCount / DeliveryYear / DeliveryQuarter / ConstructionStatus / ReadinessPercent (Developers API update-building или синк Talent)без переходата же
Смена Status: Publish (INV-BLD-4) / Hide / Unhide / Unpublish (Developers API set-building-status)Draft ↔ Published ↔ Hidden (building.md stateDiagram)та же
Пометка сдачи: ConstructionStatus → Delivered + ReadinessPercent = 100 (INV-BLD-6)без перехода Statusта же
Job project-aggregates-recalcтолько если пересчёт затронул snapshot-поле; чистый пересчёт счётчиков (PropertyObjects*Count вне payload) события не публикуетта же

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

Consumer-контекстЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
catalogПроекция корпуса в карточке проекта витрины (этажность, готовность, статус строительства) + денормализация min(DeliveryYear,DeliveryQuarter) проекта для фильтра/сортировки «срок сдачи»; только Published здания видимы гостюMessageId + guard по Version< 1 мин
dev-statsРеплика измерения Building (иерархия Developer→Project→Building для обогащения facts, ADR-0057)то же< 5 мин

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

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

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

  • Версия — один ContractVersion = 1; при несовпадении версии события — warn-and-proceed. Топик кодирует версию конвенцией суффикса .v1.
  • Additive-only для minor: новые поля — optional с безопасным default, без bump ContractVersion.
  • Breaking (удаление/переименование/смена типа/смысла поля) — новый ContractVersion = 2 + новый топик pin.chessboard.building-upserted.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum (Status, ConstructionStatus) у потребителя — fallback: warning + сохранение строки как есть; для Status поведение — как для наиболее ограничивающего (Hidden, не показывать публично); синк-fallback ConstructionStatus = UnderConstruction (reference-data.md, метрика chessboard_sync_unknown_enum_total).
  • Порядок per-building гарантирован key-ом (Id); потребители обязаны держать Version-guard.
  • Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.

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

ПолеКласс данныхПравило
Id, TenantId, ProjectId, VersionInternalinclude
Status, Name, FloorsTotal, SectionsCount, DeliveryYear, DeliveryQuarter, ConstructionStatus, ReadinessPercentPublic (юрданные/структура проектов публичны по 214-ФЗ; точный «срок сдачи» скрывается от неавторизованных потребителем — query-проекции catalog)include

Здание не несёт персональных данных — PII в payload отсутствует (маскирование неприменимо, фиксируется явно). Событие НЕ содержит secrets, tokens, credentials, платёжных реквизитов, unbounded вложений (схемы/фото хода строительства — только FileId на стороне владельца, в событие не входят).

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

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

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