Entity: CountryEntity
Дом полей справочной записи «Страна» контекста handbook. Все проекции (lookup-выдачи, CountrySnapshot,
событие pin.handbook.countries.v1, реплики потребителей) ссылаются сюда и описывают только дельту.
Назначение
CountryEntity — реплика записи страны из внешнего справочника Talent Handbook
(ADR-0063). PIN не ведёт страны
самостоятельно: записи создаёт и обновляет только синк источника; администратору платформы доступны лишь
archive/restore. На страну ссылаются регионы и города справочника (region.md,
city.md), гео-поля проектов chessboard, документы персон profile и фильтры каталога.
Классификация
| Поле | Значение |
|---|---|
| Контекст | handbook |
| Kind | Replica (source of truth — Talent Handbook) |
| Source of truth | talent.microservices.handbook, сущность Country; признак архива IsArchived — handbook |
| Таблица | handbook.countries |
| Tenant isolation | не tenanted — платформенный справочник |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | short | Да | PK | синк (= id Talent) | Идентификатор страны = id записи источника; Guid не вводится (ADR-0063 п.2). |
ShortName | string? | Нет | varchar(64) | синк | Краткое название («Россия»); реплицируется как есть. Основа display-имени. |
FullName | string | Да | varchar(64), min 1 | синк | Полное название («Российская Федерация»); реплицируется как есть. Fallback display-имени при пустом ShortName. |
IsoCode2 | string | Да | char(2), upper; unique среди неархивных ux_countries_iso2 | синк | Код ISO 3166-1 alpha-2 (RU). INV-HND-2. |
IsoCode3 | string | Да | char(3), upper; unique среди неархивных ux_countries_iso3 | синк | Код ISO 3166-1 alpha-3 (RUS). INV-HND-2. |
DefaultCurrencyId | short? | Нет | логическая ссылка на handbook.currencies (без FK — порядок прихода событий разных справочников не гарантирован) | синк | Валюта страны по умолчанию (currency.md). |
DefaultLanguageId | short? | Нет | логическая ссылка на handbook.languages (без FK) | синк | Язык страны по умолчанию (language.md). |
IsArchived | bool | Да | default false | admin / reconciliation | Soft-архив: единственный механизм вывода записи из оборота (delete-gap источника, ADR-0063 п.5). |
LastEventAt | DateTimeOffset | Да | — | синк (envelope CreatedAt применённого события источника) | Время последнего применённого события источника. У событий источника нет per-record версии — монотонность обеспечивается по времени публикации события (INV-HND-4, ADR-0063 п.1). |
CreatedAt / UpdatedAt | DateTimeOffset | Да | — | синк / admin | Штампы создания/изменения. |
В lookup-выдачах и CountrySnapshot поле Name = ShortName ?? FullName (краткое имя, а при его отсутствии
— полное) — отдельной колонки нет, вычисляется из реплицируемых ShortName/FullName (ADR-0063 п.2;
конвенция — см. snapshots.md).
Seed-факт источника: RU = 1, KZ = 2, BY = 3 — идентификаторы стабильны, на них допустимо полагаться в
конфигурации потребителей (например, дефолт страны проекта chessboard — Россия, CountryId = 1).
Связи
| Связь | Тип | Ключ | Delete behavior |
|---|---|---|---|
RegionEntity | reference (дочерние записи) | Region.CountryId → Country.Id | физических удалений нет; архив страны не каскадирует на регионы/города |
CityEntity | reference (дочерние записи) | City.CountryId → Country.Id | аналогично |
CurrencyEntity / LanguageEntity | логическая (без FK) | DefaultCurrencyId / DefaultLanguageId | нерезолвимая ссылка допустима до прихода события соответствующего справочника |
События регионов/городов, пришедшие раньше события своей страны, не теряются: синк ретраит их до появления
страны (HANDBOOK_SYNC_COUNTRY_NOT_FOUND, retriable — см.
../consumers/talent-handbook-sync.md).
Инварианты
Инварианты INV-HND-1, INV-HND-2, INV-HND-4, INV-HND-5 объявлены здесь и распространяются на все записи справочника (страны, регионы, города, валюты, языки); остальные доки контекста ссылаются на них, не переобъявляя.
| Инвариант | Условие | Когда проверяется | Ошибка |
|---|---|---|---|
| INV-HND-1 SyncOwnsData | Создание записи и изменение реплика-полей — только синк источника; администратору доступны только archive/restore. Других write-путей в контракте нет | любая мутация | — (обеспечивается составом операций) |
| INV-HND-2 UniqueIsoAmongActive | ISO-коды (IsoCode2, IsoCode3; для валют — IsoCode) уникальны среди неархивных записей своего справочника | при применении события синка; при admin restore | синк: HANDBOOK_SYNC_ISO_CONFLICT → DLQ (non-retriable); restore: тот же код, HTTP 409 |
| INV-HND-4 MonotonicByEventTime | У событий источника нет per-record версии — монотонность обеспечивается по времени публикации: событие применяется только при envelope.CreatedAt > LastEventAt реплики; иначе — skip + метрика stale. Порядок апдейтов одной записи гарантирован партиционированием Kafka по Id (ADR-0063 п.1) | при применении события синка | — (skip, не ошибка) |
| INV-HND-5 LookupVisibility | Публичный lookup выдаёт только неархивные записи; internal resolve резолвит включая архивные — исторические ссылки (CountryId в старых документах/событиях) не ломаются | при чтении (lookup / resolve) | точечное чтение несуществующего id → 404 HANDBOOK_ENTRY_NOT_FOUND; в batch-resolve неизвестный id просто отсутствует в ответе |
Жизненный цикл и операции
Статусной state-машины нет: активна ↔ архив через IsArchived.
| Операция | Актор | Предусловие | Эффект | Отказ |
|---|---|---|---|---|
| Upsert (синк) | consumer группы pin-handbook-talent-sync | envelope.CreatedAt > LastEventAt (INV-HND-4); ISO-коды не конфликтуют с другой неархивной страной (INV-HND-2) | создание/перезапись всех реплика-полей, включая ShortName/FullName (snapshot-семантика) + re-publish country-upserted | stale-время → skip; ISO-конфликт → событие в DLQ pin.handbook.talent-sync-dlq.v1 (HANDBOOK_SYNC_ISO_CONFLICT) |
| Auto-restore (синк) | consumer | запись заархивирована, из источника пришёл её upsert | IsArchived = false + warn-метрика (источник главнее локального архива) + re-publish | — |
| Archive | администратор платформы | запись существует | IsArchived = true; исчезает из lookup, остаётся резолвимой (INV-HND-5); re-publish события; повторный архив — идемпотентный no-op | 404 HANDBOOK_ENTRY_NOT_FOUND |
| Restore | администратор платформы | запись существует; ISO-коды свободны среди неархивных (INV-HND-2) | IsArchived = false; снова в lookup; re-publish | 404 HANDBOOK_ENTRY_NOT_FOUND; 409 HANDBOOK_SYNC_ISO_CONFLICT |
| Lookup (list) | любой (включая гостя) | — | неархивные страны (INV-HND-5) | — |
| Resolve (batch by id) | внутренние сервисы (m2m) | — | записи по id, включая архивные (INV-HND-5) | неизвестный id отсутствует в ответе |
Постусловие любой мутации: UpdatedAt обновлён; каждое изменение состояния re-publish-ится событием
(реплика + чекпоинт синка + публикация — атомарно, правила talent-replica.md).
События
| Событие | Топик | Когда публикуется | Payload |
|---|---|---|---|
country-upserted | pin.handbook.countries.v1 (compacted, key = Id, ContractVersion 1) | применённый upsert синка; admin archive/restore; пересинхронизация | полный snapshot реплики, включая ShortName/FullName, IsArchived и LastEventAt — ../events/country-upserted.md |
Текущий потребитель — chessboard (локальная реплика для hot-path карточек/фильтров).
Ошибки
| Код | HTTP | Условие |
|---|---|---|
HANDBOOK_ENTRY_NOT_FOUND | 404 | точечное чтение / archive / restore несуществующей страны |
HANDBOOK_SYNC_ISO_CONFLICT | 409 (admin restore) / DLQ, non-retriable (синк) | нарушение INV-HND-2: ISO-код занят другой неархивной записью |
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_countries; ux_countries_iso2 unique (upper(iso_code_2)) where not is_archived; ux_countries_iso3 unique (upper(iso_code_3)) where not is_archived |
| Партиционирование / retention | none — десятки строк; retention бессрочный (физических удалений нет) |
| Concurrency | события применяются последовательно per-key (партиция входного топика источника); admin-операции — оптимистическая блокировка |
| Permissions / PII | archive/restore — admin API (handbook.entries.manage); lookup — публичный, без авторизации; resolve — internal m2m. PII нет |
| Audit | LastEventAt + штампы; admin archive/restore логируется с актором |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (9): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity / Aggregate: PersonEntity, Entity: CityEntity, Entity: CurrencyEntity, Entity: LanguageEntity, Entity: PersonDocumentEntity, Entity: RegionEntity, Snapshot-модели справочника (единый дом платформы)
- API (4): GET /api/v1/handbook/{countries,regions,cities,currencies,languages} — Публичный lookup справочников, Handbook API — реестр операций областей public / internal / admin, POST /api/v1/admin/handbook/{type}/{id}/{archive,restore} — Архив и восстановление справочной записи, POST /internal/handbook/resolve — Batch-резолв справочных записей по id
- Use Cases (3): UC-HND-001. Администратор поддерживает справочник (archive/restore), UC-HND-002. Система синхронизирует реплику справочника из Talent, US-реестр контекста handbook (US-HND)
- События (2): Event Contract: country-upserted (CountryUpsertedEvent), Event Contract: language-upserted (LanguageUpsertedEvent)
- Консюмеры (1): Consumer: Talent Handbook Sync (pin-handbook-talent-sync)
- Фоновые задачи (1): Background Job: talent-initial-import (bootstrap и reconciliation справочников из Talent Handbook)
- index.md (1): Handbook Service
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- region.md, city.md, currency.md, language.md —
остальные записи справочника; snapshots.md —
CountrySnapshot. - talent-replica.md — правила синка и
HandbookSyncState; ../consumers/talent-handbook-sync.md — consumer. - ../events/country-upserted.md — контракт события.
- API: ../api/public/lookup-references.md, ../api/internal/resolve-references.md, ../api/admin/archive-restore-entry.md.
- ADR-0063.