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

Entity: CityEntity

Дом полей справочной записи «Город» контекста handbook. Проекции (lookup-выдачи с пагинацией, CitySnapshot, событие pin.handbook.cities.v1, гео-поля проектов chessboard, город лида crm, CityRefModel в user-activity) ссылаются сюда и описывают только дельту.

Назначение

CityEntity — реплика записи города из Talent Handbook (ADR-0063). Самый объёмный и самый востребованный справочник контекста: города — ключ гео-фильтров каталога, адресов проектов и пожеланий лидов. Записи создаёт и обновляет только синк источника; администратору доступны archive/restore.

Классификация

ПолеЗначение
Контекстhandbook
KindReplica (source of truth — Talent Handbook)
Source of truthtalent.microservices.handbook, сущность City; признак архива IsArchivedhandbook
Таблицаhandbook.cities
Tenant isolationне tenanted — платформенный справочник

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdlongДаPKсинк (= id Talent)Идентификатор города = id записи источника (ADR-0063 п.2).
CountryIdshortДаFK → handbook.countries; index ix_cities_country_idсинкСтрана города.
RegionIdlong?НетFK → handbook.regions; index ix_cities_region_idсинкРегион города; nullable — источник допускает города вне региона (напр., города федерального значения).
NamestringДаvarchar(256), min 1; index ix_cities_name (lower(name))синкНазвание города («Самара»).
IsArchivedboolДаdefault falseadmin / reconciliationSoft-архив (delete-gap источника, ADR-0063 п.5).
LastEventAtDateTimeOffsetДасинк (envelope CreatedAt применённого city-события)Время последнего применённого события источника. У событий источника нет per-record версии — монотонность обеспечивается по времени публикации события (INV-HND-4, ADR-0063 п.1).
CreatedAt / UpdatedAtDateTimeOffsetДасинк / adminШтампы создания/изменения.

Связи

СвязьТипКлючDelete behavior
CountryEntityreference (родитель)CountryId → Country.Idфизических удалений нет
RegionEntityreference (родитель, опционально)RegionId → Region.Idархив региона не каскадирует на города
Порядок применения geo-событий

Синк применяет цепочку строго по порядку: страна → регион (derive из этого же city-события, region.md) → город. City-событие, пришедшее раньше события своей страны, ретраится (HANDBOOK_SYNC_COUNTRY_NOT_FOUND), а не теряется.

Инварианты

INV-HND-1 (создаёт/меняет только синк), INV-HND-2 (не применим — ISO-кодов нет), INV-HND-4 (монотонность по LastEventAt — envelope CreatedAt события, per-record версии у источника нет), INV-HND-5 (lookup — только неархивные; resolve — включая архивные) — объявлены в country.md и действуют для города без изменений. INV-HND-3 GeoConsistency объявлен здесь (его аспект для derive региона — region.md):

ИнвариантУсловиеКогда проверяетсяОшибка
INV-HND-3 GeoConsistencyСогласованность цепочки city ↔ region ↔ country: страна города существует; если RegionId задан — регион существует и Region.CountryId = City.CountryId (город не может ссылаться на регион чужой страны)при применении city-события синкастраны нет → HANDBOOK_SYNC_COUNTRY_NOT_FOUND (retriable); регион чужой страны / рассогласование → HANDBOOK_SYNC_GEO_MISMATCH → DLQ pin.handbook.talent-sync-dlq.v1 (non-retriable)
Нарушение INV-HND-3 — дефект данных источника

Нарушение INV-HND-3 всегда означает дефект данных источника: событие уходит в DLQ, разрешение — правка данных в Talent Handbook и replay (автопочинки на стороне PIN нет).

Жизненный цикл и операции

ОперацияАкторПредусловиеЭффектОтказ
Upsert (синк)consumer группы pin-handbook-talent-syncenvelope.CreatedAt > LastEventAt (INV-HND-4); гео-цепочка согласована (INV-HND-3)создание/перезапись всех реплика-полей (snapshot-семантика) + re-publish city-upsertedstale → skip; страны нет → retry; гео-рассогласование → DLQ
Auto-restore (синк)consumerзапись заархивирована, из источника пришёл её upsertIsArchived = false + warn-метрика + re-publish
Archive / Restoreадминистратор платформызапись существуетIsArchived переключён; re-publish; повторный вызов — идемпотентный no-op404 HANDBOOK_ENTRY_NOT_FOUND
Lookup (list/поиск по имени)любой (включая гостя)неархивные города (INV-HND-5); фильтры по стране/региону, поиск по префиксу имени, пагинация (единственный справочник, где выдача постранична — тысячи строк)
Resolve (batch by id)внутренние сервисы (m2m: write-path-валидация crm/profile, мягкая валидация user-activity)записи по id, включая архивные (INV-HND-5)неизвестный id отсутствует в ответе

События

СобытиеТопикКогда публикуетсяPayload
city-upsertedpin.handbook.cities.v1 (compacted, key = Id, ContractVersion 1)применённый upsert синка; admin archive/restore; пересинхронизацияполный snapshot реплики, включая IsArchived и LastEventAt../events/city-upserted.md
Текущий потребитель

Текущий потребитель — chessboard (локальная реплика; при переименовании города обновляет денормализованные CityName проектов и re-publish-ит затронутые проекты).

Ошибки

КодHTTPУсловие
HANDBOOK_ENTRY_NOT_FOUND404точечное чтение / archive / restore несуществующего города
HANDBOOK_SYNC_COUNTRY_NOT_FOUND— (синк, retriable)city-событие пришло раньше события своей страны; retry с backoff, затем DLQ + алерт
HANDBOOK_SYNC_GEO_MISMATCH— (синк, non-retriable → DLQ)нарушение INV-HND-3: регион города принадлежит другой стране либо не существует после ретраев

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK pk_cities; ix_cities_country_id; ix_cities_region_id; ix_cities_name (lower(name)) — префиксный поиск в lookup (PostgreSQL, без ES — ADR-0063 п.7)
Партиционирование / retentionnone — тысячи строк; retention бессрочный
Concurrencyсобытия применяются последовательно per-key; admin-операции — оптимистическая блокировка
Permissions / PIIarchive/restore — admin API (handbook.entries.manage); lookup — публичный; resolve — internal m2m. PII нет
AuditLastEventAt + штампы; admin archive/restore логируется с актором

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

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

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