Snapshot-модели справочника (единый дом платформы)
Единственный дом snapshot-моделей справочных записей для всей платформы: CountrySnapshot,
RegionSnapshot, CitySnapshot, CurrencySnapshot. Контракты остальных сервисов (payload событий, модели
ответов, хранимые документы) ссылаются сюда и описывают только использование — поля моделей нигде не
переописываются.
Назначение
Snapshot — вложенная display-модель справочной записи, зафиксированная в контракте (обычно — в payload
события) на момент публикации. Даёт потребителю человекочитаемые имена без синхронного резолва в
handbook и сохраняет исторический след: как называлась запись в момент факта, даже если позже её
переименовали или заархивировали.
Классификация
| Поле | Значение |
|---|---|
| Контекст | handbook (дом моделей); используются контрактами всех сервисов |
| Kind | Snapshot (иммутабельная вложенная модель, не хранимая таблица handbook) |
| Source of truth полей | реплики справочника: country.md, region.md, city.md, currency.md |
Модели
CountrySnapshot
| Поле | Тип | Обязательность | Ограничения | Источник (поле реплики) |
|---|---|---|---|---|
CountryId | short | Да | — | CountryEntity.Id |
IsoCode2 | string | Да | char(2), ISO 3166-1 alpha-2 | CountryEntity.IsoCode2 |
Name | string | Да | ≤ 64 символов | CountryEntity.ShortName ?? FullName (ADR-0063 п.2) |
CountrySnapshot.NameУ источника Country.ShortName необязателен (краткое имя, напр. «Россия»), FullName обязателен (полное,
напр. «Российская Федерация»); каноническое отображаемое имя страны в PIN — ShortName, а при его
отсутствии — FullName (country.md, ADR-0063 п.2). Snapshot фиксирует уже
разрешённое значение: при пустом ShortName в snapshot попадает FullName (отдельного признака «какое из
имён взято» контракт не несёт).
RegionSnapshot
| Поле | Тип | Обязательность | Ограничения | Источник (поле реплики) |
|---|---|---|---|---|
RegionId | long | Да | — | RegionEntity.Id |
Name | string | Да | ≤ 256 символов | RegionEntity.Name |
CitySnapshot
| Поле | Тип | Обязательность | Ограничения | Источник (поле реплики) |
|---|---|---|---|---|
CityId | long | Да | — | CityEntity.Id |
Name | string | Да | ≤ 256 символов | CityEntity.Name |
CurrencySnapshot
| Поле | Тип | Обязательность | Ограничения | Источник (поле реплики) |
|---|---|---|---|---|
CurrencyId | short | Да | — | CurrencyEntity.Id |
IsoCode | string | Да | char(3), ISO-4217 (уже нормализован репликой) | CurrencyEntity.IsoCode |
Name | string | Да | ≤ 64 символов | CurrencyEntity.Name |
Sign | string? | Нет | ≤ 16 символов | CurrencyEntity.Sign |
IsoCodeDisplay-код валюты (IsoCode) в контрактах потребителей живёт только внутри CurrencySnapshot —
отдельных строковых полей кода валюты контракты не несут (currency.md).
IsoCode snapshot-а — нормализованный char(3) реплики: источник хранит код в varchar(16), но значения
длиной ≠ 3 в реплику и, следовательно, в snapshot не попадают (reject на синке, INV-HND-6 —
currency.md); в snapshot всегда валидный ISO-4217.
Связи
Каждая snapshot-модель фиксирует значение своей реплики справочника на момент публикации несущего контракта (один источник — множество снапшотов во времени, по одному на каждую публикацию):
Правила использования
- Деньги. Любое событие/контракт с денежной суммой несёт типизированную ссылку
CurrencyId short+CurrencySnapshot. Snapshot — display и юридический след; допустимость валюты в операции ограничена ADR-0048 (RUB-only, справочник ≠ мультивалютность). - Гео. События/контракты с географией несут id (
CountryId/RegionId/CityId) + соответствующие снапшоты имён (CountrySnapshot/RegionSnapshot/CitySnapshot); состав — по потребности контракта (например, карточка проекта — все три, лид — только город). - Фиксация на момент публикации. Snapshot снимается в момент публикации события/записи документа и
не «догоняется»: последующие переименования или архив записи справочника уже опубликованные payload-ы
не меняют. Актуальные значения потребитель получает из
pin.handbook.*-топиков, публичного lookup или internal resolve — не из старых снапшотов. - Единый дом. Контракты других сервисов ссылаются на этот документ и не переописывают поля моделей;
в контракте указывается только факт использования («
Price.CurrencyId+CurrencySnapshot— см. handbook/domain/snapshots.md»). - Архивные записи. Snapshot корректно строится и для архивной записи: internal resolve резолвит включая архивные (INV-HND-5, country.md) — исторические события и документы, ссылающиеся на выведенную из оборота запись, остаются полными.
- Хранение у потребителей. Канонический паттерн хранимых сущностей —
<X>Id(тип источника) + денормализованное display-имя<X>Name, снятое при записи (ADR-0063 п.6); полный snapshot — в payload событий. Обновление денормализованных имён при переименованиях — ответственность потребителя с локальной репликой (chessboard), остальные живут с историческим именем до перезаписи сущности.
Совместимость
- Snapshot-модели вкладываются в контракты потребителей и версионируются вместе с несущим контрактом
(отдельного
ContractVersionу snapshot-модели нет). - Изменения моделей — только additive (новые nullable-поля); переименование/удаление/смена типа поля — breaking сразу для всех несущих контрактов платформы и требует сводной ревизии.
- Неизвестные поля snapshot-а потребитель игнорирует.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (21): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity / Aggregate: BookingEntity, Entity / Aggregate: CatalogProjectCard, Entity / Aggregate: CommissionAccrualEntity, Entity / Aggregate: DealEntity, Entity / Aggregate: InvestorPropertyEntity, Entity / Aggregate: LeadEntity, Entity / Aggregate: MessageEntity (TPH: Text/Attachment/EventCard), Entity / Aggregate: PayoutOrderEntity, Entity / Aggregate: PlatformCommissionTermsEntity, Entity / Aggregate: PlatformReceiptEntity (+9)
- API (19): GET /api/developer/crm/deals/{dealId} — Карточка сделки (застройщик), GET /api/v1/catalog/filters — Справочники значений фильтров, GET /api/v1/platform/wallet/accounts — Реестр счетов кошелька, GET /api/v1/platform/wallet/accounts/{accountId}/ledger — Журнал проводок счёта, Handbook API — реестр операций областей public / internal / admin, Investor-Owner API — реестр операций и общие контрактные модели, POST /api/developer/crm/booking-programs — Создать программу бронирования, POST /api/v1/admin/portfolio/properties — Создание записи портфеля вручную (admin, компенсирующее), POST /api/v1/developers/property-objects/{propertyObjectId}/price — Изменение цены объекта недвижимости, POST /api/v1/internal/property-objects/by-ids — Батч-выдача объектов недвижимости по идентификаторам (internal, для catalog/crm), POST /api/v1/platform/receipts — Создание акта поступления средств (черновик), POST /api/v1/platform/receipts/{receiptId}/register — Регистрация акта поступления средств (+7)
- Use Cases (7): UC-CHS-003. Изменение цены объекта застройщиком (события в аналитику), UC-CHS-004. Просмотр шахматки проекта: гость vs инвестор (разница полей), UC-HND-001. Администратор поддерживает справочник (archive/restore), UC-HND-002. Система синхронизирует реплику справочника из Talent, UC-OWN-002. Завершение сделки в CRM → купленный объект в «Мои объекты недвижимости» (system), UC-RWD-003. Сделка завершена — начисление комиссии (system), US-реестр контекста handbook (US-HND)
- События (21): Event Contract: city-upserted (CityUpsertedEvent), Event Contract: country-upserted (CountryUpsertedEvent), Event Contract: currency-upserted (CurrencyUpsertedEvent), Event Contract: pin.chessboard.project-upserted (ProjectUpsertedKafkaEvent), Event Contract: pin.chessboard.property-object-price-changed (PropertyObjectPriceChangedKafkaEvent), Event Contract: pin.chessboard.property-object-upserted (PropertyObjectUpsertedKafkaEvent), Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.booking-paid (BookingPaidEvent), Event Contract: pin.crm.deal-completed (DealCompletedEvent), Event Contract: pin.investor-owner.property-added (InvestorPropertyAddedKafkaEvent), Event Contract: pin.investor-owner.property-sold (InvestorPropertySoldKafkaEvent), Event Contract: pin.promotion.purchase-changed (PromotionPurchaseChangedKafkaEvent) (+9)
- Консюмеры (7): Consumer: завершённая сделка CRM → объект в портфеле инвестора (pin-investor-owner-crm-deal-completed), Consumer: синхронизация из Talent Owners (pin-investor-owner-talent-owners-sync), Consumer: Booking Paid Card (MessengerBookingPaidConsumer), Consumer: Chessboard Projection Sync (pin-catalog-chessboard-sync), Consumer: CRM Deal Completed, Consumer: CRM PropertyObject Status (pin-chessboard-crm-property-object-status), Consumer: DevStatsProjectUpsertedConsumer
- dimensions (1): dim_project (ClickHouse dimension)
- index.md (4): Handbook Service, Promotion Service (Pin.Promotion), Rewards Service, Wallet Service
- registries (1): Реестр сущностей PIN
- api-documentation-rules.md (1): Правила Документирования API Методов
- event-documentation-rules.md (1): Правила Документирования Событий
- strict-typed-documentation-rules.md (1): Правила Строгой Типизации Документации
Связанные документы
- Дома полей реплик: country.md, region.md, city.md, currency.md; инварианты INV-HND-1/2/4/5 — country.md, INV-HND-3 — city.md.
- Правила ссылок на справочники в контрактах: strict-typed-documentation-rules.md.
- Resolve архивных записей: ../api/internal/resolve-references.md.
- ADR-0063 п.6, ADR-0048.