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

Event Contract: pin.chessboard.layout-upserted (LayoutUpsertedKafkaEvent)

Версионируемый контракт интеграционного события «планировка создана/обновлена/архивирована». Consumer-документы ссылаются сюда, не переописывая payload. Это единственный интеграционный сигнал агрегата LayoutEntity: паспортный snapshot планировки для create, update и архива (IsArchived = true, soft-delete). Отдельного события «схемы/dimensions» не существует — изменение схем планировки распространяется этим же событием (см. примечание в конце).

Назначение

Фиксирует создание LayoutEntity (Developers API create-layout, синк Talent Chessboard) и каждое обновление её публичных полей (Name, Rooms, площади, LayoutGroupId-привязка, схемы), а также перевод в архив (archive-layout). Событие публичное: даёт downstream-контекстам snapshot планировки без синхронного запроса в chessboard — измерение Layout статистики застройщика (ADR-0057), условия правил вознаграждений по планировке, типизацию объектов недвижимости эконом-модели и проекцию планировки в карточке объекта недвижимости витрины.

Metadata

ПолеЗначение
Event typepin.chessboard.layout-upserted
C# typeLayoutUpsertedKafkaEvent
Topicpin.chessboard.layout-upserted.v1
Partitions / key3 партиции (планировок — тысячи, много меньше объектов недвижимости) / key = Id — порядок per-layout гарантирован
ContractVersion1
Producerchessboard: Developers API create-layout / update-layout / archive-layout; синк Talent (планировка upsert-ится вложенно, talent-chessboard-sync)
AggregateTypeLayout
Статусdraft

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

Payload LayoutUpsertedKafkaEvent

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

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
IdGuidДаLayoutEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаLayoutEntity.TenantIdImmutableTenant boundary = DeveloperId проекта (ADR-0052).
ProjectIdGuidДаLayoutEntity.ProjectIdImmutableПроект-владелец (ключ иерархии измерений dev-stats: …→ProjectId→LayoutGroupId→LayoutId→…).
LayoutGroupIdGuid?НетLayoutEntity.LayoutGroupIdAdditive onlyГруппа планировок (уровень rewards/dev-stats); null — планировка вне группы (INV-LAY-3: группа того же проекта).
NamestringДаLayoutEntity.NameAdditive onlyНазвание: «1К-А 32.5», «Студия S» (≤ 128, уникально в проекте, INV-LAY-2).
RoomsshortДаLayoutEntity.RoomsAdditive onlyТиповая комнатность; 0 = студия (0..20).
TotalAreadecimalДаLayoutEntity.TotalAreaAdditive onlyТиповая общая площадь, м² (> 0).
LivingAreadecimal?НетLayoutEntity.LivingAreaAdditive onlyТиповая жилая площадь, м² (> 0 и ≤ TotalArea).
PropertyObjectsCountintДаLayoutEntity.PropertyObjectsCount (background project-aggregates-recalc)Additive onlyДенормализованный счётчик объектов недвижимости с этой планировкой (≥ 0); eventually consistent.
IsArchivedboolДаLayoutEntity.DeletedAt != nullAdditive onlyПризнак архива (soft-delete); true — планировка выведена (архив разрешён только без живых объектов недвижимости, INV-LAY-5).
VersionlongДаLayoutEntity.VersionImmutable semanticsДоменная версия агрегата; монотонный guard у потребителей (событие с меньшей/равной версией — skip). Version = 1 ⇔ создание планировки.
Поля вне payload

Вложенных моделей нет — все поля скалярные. Тяжёлый jsonb Schemas (LayoutSchemasDocument), а также Description, ExternalTalentLayoutId, SyncSource, ReplicaVersion, audit-поля в событие не входят: схемы не инлайнятся (без unbounded вложений, живут на агрегате layout.md); реплика-метаданные синка служебны (граница идемпотентности consumer-а синка, не публичный контракт).

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

УсловиеПереход состоянияСогласованность
Создание планировки: Developers API create-layout или вложенный upsert при синке Talent (планировка заводится вместе с объектом недвижимости)[*] → активна (IsArchived = false, Version = 1)публикация атомарна со сменой состояния агрегата
Обновление публичных полей: Name / Rooms / TotalArea / LivingArea / LayoutGroupId / Schemas / Description (Developers API update-layout; реплика-поля — синк Talent)без перехода (bump Version)та же
Пересчёт денормализации PropertyObjectsCount (background project-aggregates-recalc при появлении/архиве объектов недвижимости планировки)без перехода (bump Version)та же
Архив планировки: Developers API archive-layoutDeletedAt = now, guard INV-LAY-5активна → архив (IsArchived = true)та же
Delete-события нет

Отдельного delete-события нет — архив доставляется как upsert с IsArchived = true (потребители перезаписывают свою реплику и деактивируют планировку).

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

Consumer-контекстЭффект (событие → что меняется)ИдемпотентностьЗадержка (SLA)
dev-statsРеплика измерения Layout (уровень фильтрации/обогащения facts в иерархии …→LayoutGroup→Layout→PropertyObject, ADR-0057); IsArchived = true → пометка измерения архивнымMessageId + guard по Version< 5 мин
rewardsРеестр планировок для условий правил вознаграждений по Layout (комнатность/площади/группа); IsArchived = true → правила по планировке неактивныто же< 5 мин
invest-analyticsТипизация объектов недвижимости эконом-модели по планировке (комнатность/площади)то же< 5 мин
catalogПроекция планировки в карточке объекта недвижимости витрины (название/комнатность/площади); IsArchived = true скрывает планировкуто же< 5 мин

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

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

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

  • Версия — один ContractVersion = 1; при несовпадении версии события — warn-and-proceed. Топик кодирует версию конвенцией суффикса .v1.
  • Additive-only для minor: новые поля — optional с безопасным default, без bump.
  • Breaking (удаление/переименование/смена типа/смысла поля) — новый ContractVersion = 2 + новый топик pin.chessboard.layout-upserted.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum handling — неприменимо: перечислимых enum-полей в payload нет (все поля скалярные; IsArchivedbool). Схемы (LayoutSchemaKind) в событие не входят.
  • Порядок per-layout гарантирован key-ом (Id); потребители обязаны держать Version-guard (upsert создания, обновления, пересчёта и архива одного агрегата упорядочены между собой).
  • Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.

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

ПолеКласс данныхПравило
Id, TenantId, ProjectId, LayoutGroupId, Version, PropertyObjectsCount, IsArchivedInternalinclude
Name, Rooms, TotalArea, LivingAreaPublicinclude (планировки published-проектов видны всем в карточке объекта недвижимости, layout.md)
PII отсутствует

PII в событии нет: планировка — типовая справочная сущность без персональных данных (юрданные проектов публичны по 214-ФЗ), поэтому маскирование payload не требуется. В логах фигурируют только идентификаторы (Id/ProjectId/TenantId) — не чувствительны. Событие НЕ содержит secrets, tokens, raw credentials, платёжных данных, unbounded вложений: схемы планировки (LayoutSchemasDocument) — часть агрегата LayoutEntity, в payload не инлайнятся, и отдельного события dimensions-upserted не существует. Доступ к топику — m2m ACL Kafka (dev-stats/rewards/invest-analytics/catalog); tenant-изоляция обеспечивается наличием TenantId в payload.

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

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

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

  • Источники (операции): create-layout.md, update-layout.md, archive-layout.md; чтение — list-layouts.md; синк — talent-chessboard-sync.md (вложенный upsert планировки); пересчёт PropertyObjectsCount — background project-aggregates-recalc.
  • Сущность: layout.md (дом полей, инварианты INV-LAY-*, состав события); группа — layout-group-upserted.md; соседнее событие агрегата шахматки — property-object-upserted.md; reference data — reference-data.md.
  • Примечание: схемы планировок не выносятся в отдельное событие dimensions-upserted — они покрываются агрегатом Layout и распространяются pin.chessboard.layout-upserted.v1.
  • ADR-0052 (tenancy), ADR-0056 (simplicity-first), ADR-0057 (dev-stats потребляет измерения).