Entity: RegionEntity
Дом полей справочной записи «Регион» контекста handbook. Проекции (lookup-выдачи, RegionSnapshot,
событие pin.handbook.regions.v1, фильтры каталога, гео-поля проектов chessboard) ссылаются сюда и
описывают только дельту.
Назначение
RegionEntity — регион (субъект/область) страны. В PIN это первоклассная запись справочника со своим
топиком и идентификатором, хотя у источника (Talent Handbook) собственного агрегата и события региона нет:
строка региона приходит только вложенной моделью RegionEventModel внутри city-событий и derive-ится
синком (ADR-0063 п.4). На регион
ссылаются города справочника (city.md) и фильтр-ключ RegionId каталога/карточек проектов.
Классификация
| Поле | Значение |
|---|---|
| Контекст | handbook |
| Kind | Replica (derive из city-событий источника; source of truth — Talent Handbook, сущность CountryRegion) |
| Source of truth | talent.microservices.handbook (через вложенную RegionEventModel city-событий); признак архива IsArchived — handbook |
| Таблица | handbook.regions |
| Tenant isolation | не tenanted — платформенный справочник |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | long | Да | PK | синк (= id CountryRegion Talent) | Идентификатор региона = id записи источника (ADR-0063 п.2). |
CountryId | short | Да | FK → handbook.countries; index ix_regions_country_id | синк (= CountryId вмещающего city-события) | Страна региона. RegionEventModel источника CountryId не несёт — страна берётся из city-события, внутри которого регион приехал (ADR-0063 п.4). INV-HND-3: регион принадлежит ровно одной стране. |
Name | string | Да | varchar(256), min 1 | синк (RegionEventModel.Name) | Название региона («Самарская область»). |
IsArchived | bool | Да | default false | admin / reconciliation | Soft-архив (delete-gap источника, ADR-0063 п.5). |
LastEventAt | DateTimeOffset | Да | — | синк (envelope CreatedAt city-события) | Время последнего применённого события источника. У источника нет собственного версионирования региона (и per-record версии у событий вообще нет) — монотонность обеспечивается по времени события (INV-HND-4, ADR-0063 п.1). |
CreatedAt / UpdatedAt | DateTimeOffset | Да | — | синк / admin | Штампы создания/изменения. |
Особенности наполнения (derive из city-событий)
- Отдельного region-события у источника нет. При применении city-события
(
handbooks.after-city-information-update) синк берёт вложеннуюRegionEventModel = { RegionId (long), Name }(безCountryId) и создаёт/обновляет запись региона;CountryIdрегиона берётся изCountryIdвмещающего city-события (город, регион и страна в одном событии консистентны, ADR-0063 п.4). Регион без единого пришедшего города в PIN не материализуется (нет источника егоCountryId). Изменение региона re-publish-ится собственным событиемregion-upserted. - Монотонность — по времени события: применяется только при
envelope.CreatedAt city-события > LastEventAt; более старое событие — skip + метрика stale (INV-HND-4 в time-based-форме). Порядок апдейтов гарантирован партиционированием Kafka поIdзаписи-источника. - Порядок применения geo-справочников: страна → регион → город. Если страна региона ещё не реплицирована —
retriable-ошибка
HANDBOOK_SYNC_COUNTRY_NOT_FOUND(retry с backoff, событие не теряется).
Переименование региона в источнике доезжает до PIN только вместе с city-событием любого из его городов. Если ни одно city-событие не пришло — имя региона в реплике устаревает. Ограничение принято как допустимое для редко меняющихся geo-данных и гарантированно закрывается полной пересинхронизацией (../background/talent-initial-import.md); staleness отслеживается метрикой (см. ../consumers/talent-handbook-sync.md).
Связи
| Связь | Тип | Ключ | Delete behavior |
|---|---|---|---|
CountryEntity | reference (родитель) | CountryId → Country.Id | физических удалений нет; архив страны не каскадирует |
CityEntity | reference (дочерние записи) | City.RegionId → Region.Id | архив региона не каскадирует на города |
Инварианты
INV-HND-1 (создаёт/меняет только синк), INV-HND-2 (не применим — у региона нет ISO-кодов), INV-HND-5 (lookup — только неархивные; resolve — включая архивные) — объявлены в country.md; INV-HND-3 GeoConsistency объявлен в city.md. Ниже — аспект INV-HND-3 для региона и time-based-форма INV-HND-4:
| Инвариант | Условие | Когда проверяется | Ошибка |
|---|---|---|---|
| INV-HND-3 GeoConsistency (аспект региона) | Регион принадлежит ровно одной стране: CountryId вмещающего city-события (страна региона RegionEventModel не несёт) совпадает с CountryId существующей записи региона (перенос региона между странами источником не поддерживается) | при применении city-события | HANDBOOK_SYNC_GEO_MISMATCH → DLQ pin.handbook.talent-sync-dlq.v1 (non-retriable, разрешение — правка данных в источнике) |
| INV-HND-4 MonotonicByEventTime (time-based-форма) | Регион применяется только при envelope.CreatedAt city-события строго больше LastEventAt | при применении city-события | — (skip + метрика stale) |
Жизненный цикл и операции
| Операция | Актор | Предусловие | Эффект | Отказ |
|---|---|---|---|---|
| Upsert (derive из city-события) | consumer группы pin-handbook-talent-sync | страна (CountryId city-события) существует; envelope.CreatedAt > LastEventAt; CountryId не меняется (INV-HND-3) | создание/обновление Name/LastEventAt + re-publish region-upserted (только если запись фактически изменилась) | страны нет → retry HANDBOOK_SYNC_COUNTRY_NOT_FOUND; смена страны → DLQ HANDBOOK_SYNC_GEO_MISMATCH; stale → skip |
| Auto-restore (синк) | consumer | запись заархивирована, регион снова пришёл в city-событии | IsArchived = false + warn-метрика + re-publish | — |
| Archive / Restore | администратор платформы | запись существует | IsArchived переключён; re-publish; повторный вызов — идемпотентный no-op | 404 HANDBOOK_ENTRY_NOT_FOUND |
| Lookup (list по стране) | любой (включая гостя) | — | неархивные регионы страны (INV-HND-5) | — |
| Resolve (batch by id) | внутренние сервисы (m2m) | — | записи по id, включая архивные (INV-HND-5) | неизвестный id отсутствует в ответе |
События
| Событие | Топик | Когда публикуется | Payload |
|---|---|---|---|
region-upserted | pin.handbook.regions.v1 (compacted, key = Id, ContractVersion 1) | фактическое изменение записи при derive из city-события; admin archive/restore; пересинхронизация | полный snapshot реплики, включая IsArchived и LastEventAt — ../events/region-upserted.md |
chessboard (локальная реплика; при переименовании региона обновляет денормализованные RegionName
проектов и re-publish-ит затронутые проекты).
Ошибки
| Код | HTTP | Условие |
|---|---|---|
HANDBOOK_ENTRY_NOT_FOUND | 404 | точечное чтение / archive / restore несуществующего региона |
HANDBOOK_SYNC_COUNTRY_NOT_FOUND | — (синк, retriable) | city-событие с регионом пришло раньше события своей страны; retry с backoff, затем DLQ + алерт |
HANDBOOK_SYNC_GEO_MISMATCH | — (синк, non-retriable → DLQ) | нарушение INV-HND-3: CountryId вмещающего city-события не совпадает со страной существующего региона |
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_regions; ix_regions_country_id; ix_regions_name (lower(name)) — поиск в lookup |
| Партиционирование / 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) (7): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity: CityEntity, Entity: CountryEntity, Entity: CurrencyEntity, Entity: LanguageEntity, Snapshot-модели справочника (единый дом платформы)
- API (5): GET /api/v1/admin/handbook/sync-state — Состояние синхронизации справочников с Talent, 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)
- События (1): Event Contract: region-upserted (RegionUpsertedEvent)
- Консюмеры (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 (1): Реестр сущностей PIN
Связанные документы
- country.md — родитель + INV-HND-1/2/4/5; city.md — источник derive;
snapshots.md —
RegionSnapshot. - talent-replica.md — правила синка; ../consumers/talent-handbook-sync.md — consumer; ../events/region-upserted.md — контракт события.
- ../background/talent-initial-import.md — полная пересинхронизация (закрывает ограничение переименований).
- ADR-0063 п.4.