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 type | pin.chessboard.layout-upserted |
| C# type | LayoutUpsertedKafkaEvent |
| Topic | pin.chessboard.layout-upserted.v1 |
| Partitions / key | 3 партиции (планировок — тысячи, много меньше объектов недвижимости) / key = Id — порядок per-layout гарантирован |
| ContractVersion | 1 |
| Producer | chessboard: Developers API create-layout / update-layout / archive-layout; синк Talent (планировка upsert-ится вложенно, talent-chessboard-sync) |
| AggregateType | Layout |
| Статус | draft |
Событие несёт метаданные: идентификатор сообщения (для дедупликации), время, идентификатор и тип
агрегата-источника (AggregateId = Id, AggregateType = "Layout"); сквозной trace восстанавливается из
метаданных. Публикуется атомарно со сменой состояния агрегата (транзакционный outbox).
Payload LayoutUpsertedKafkaEvent
Дом полей — entity: LayoutEntity; здесь перечислено только то, что реально в
событии.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | LayoutEntity.Id | Immutable | Ключ агрегата (= Kafka key). |
TenantId | Guid | Да | LayoutEntity.TenantId | Immutable | Tenant boundary = DeveloperId проекта (ADR-0052). |
ProjectId | Guid | Да | LayoutEntity.ProjectId | Immutable | Проект-владелец (ключ иерархии измерений dev-stats: …→ProjectId→LayoutGroupId→LayoutId→…). |
LayoutGroupId | Guid? | Нет | LayoutEntity.LayoutGroupId | Additive only | Группа планировок (уровень rewards/dev-stats); null — планировка вне группы (INV-LAY-3: группа того же проекта). |
Name | string | Да | LayoutEntity.Name | Additive only | Название: «1К-А 32.5», «Студия S» (≤ 128, уникально в проекте, INV-LAY-2). |
Rooms | short | Да | LayoutEntity.Rooms | Additive only | Типовая комнатность; 0 = студия (0..20). |
TotalArea | decimal | Да | LayoutEntity.TotalArea | Additive only | Типовая общая площадь, м² (> 0). |
LivingArea | decimal? | Нет | LayoutEntity.LivingArea | Additive only | Типовая жилая площадь, м² (> 0 и ≤ TotalArea). |
PropertyObjectsCount | int | Да | LayoutEntity.PropertyObjectsCount (background project-aggregates-recalc) | Additive only | Денормализованный счётчик объектов недвижимости с этой планировкой (≥ 0); eventually consistent. |
IsArchived | bool | Да | LayoutEntity.DeletedAt != null | Additive only | Признак архива (soft-delete); true — планировка выведена (архив разрешён только без живых объектов недвижимости, INV-LAY-5). |
Version | long | Да | LayoutEntity.Version | Immutable semantics | Доменная версия агрегата; монотонный guard у потребителей (событие с меньшей/равной версией — skip). Version = 1 ⇔ создание планировки. |
Вложенных моделей нет — все поля скалярные. Тяжёлый 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-layout — DeletedAt = now, guard INV-LAY-5 | активна → архив (IsArchived = true) | та же |
Отдельного 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). Архив доставляется как upsertIsArchived = 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 нет (все поля скалярные;
IsArchived—bool). Схемы (LayoutSchemaKind) в событие не входят. - Порядок per-layout гарантирован key-ом (
Id); потребители обязаны держатьVersion-guard (upsert создания, обновления, пересчёта и архива одного агрегата упорядочены между собой). - Deprecated-версия — sunset через 2 релиза после миграции последнего потребителя.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
Id, TenantId, ProjectId, LayoutGroupId, Version, PropertyObjectsCount, IsArchived | Internal | include |
Name, Rooms, TotalArea, LivingArea | Public | include (планировки published-проектов видны всем в карточке объекта недвижимости, layout.md) |
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.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): POST /api/v1/developers/layouts/{layoutId}/archive — Архивирование планировки, POST /api/v1/developers/projects/{projectId}/layouts — Создание планировки, PUT /api/v1/developers/layouts/{layoutId} — Обновление планировки
- События (2): Event Contract: pin.chessboard.layout-group-upserted (LayoutGroupUpsertedKafkaEvent), Event Contract: pin.chessboard.property-object-upserted (PropertyObjectUpsertedKafkaEvent)
- Консюмеры (2): Consumer: DevStatsLayoutUpsertedConsumer, Consumer: Talent Chessboard Sync (pin-chessboard-talent-sync)
- Фоновые задачи (2): Background Job: project-aggregates-recalc (пересчёт денормализованных агрегатов), Background Job: talent-initial-import (первичная загрузка / bootstrap из Talent Chessboard)
- dimensions (1): dim_layout (ClickHouse dimension)
- registries (1): Реестр событий PIN
Связанные документы
- Источники (операции): create-layout.md,
update-layout.md,
archive-layout.md; чтение —
list-layouts.md; синк —
talent-chessboard-sync.md (вложенный upsert планировки);
пересчёт
PropertyObjectsCount— backgroundproject-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 потребляет измерения).