Event Contract: city-upserted (CityUpsertedEvent)
Версионируемый контракт интеграционного события «запись города справочника создана/изменена».
Consumer-документы ссылаются сюда, не переописывая payload. Дом полей —
entity: CityEntity.
Назначение
Фиксирует каждое изменение состояния реплики города в контексте handbook — самого объёмного и самого
востребованного справочника: города — ключ гео-фильтров каталога, адресов проектов и пожеланий лидов.
Помимо полей записи payload несёт вложенные снапшоты родительской географии (CountrySnapshot,
RegionSnapshot?), чтобы потребитель получал человекочитаемые имена страны/региона без ожидания событий
соседних топиков справочника. Compacted-топик служит источником начальной загрузки (чтение с offset 0).
Metadata
| Поле | Значение |
|---|---|
| Event type | city-upserted |
| Topic | pin.handbook.cities.v1 — compacted (cleanup.policy = compact); последняя запись каждого города хранится бессрочно |
| Partitions / key | 1 (тысячи строк, редкие изменения, строгий порядок) / key = Id записи |
| ContractVersion | 1 |
| Producer | handbook: применённый синк (talent-handbook-sync), admin archive/restore (archive-restore-entry), полная пересинхронизация (talent-initial-import) |
| Статус | draft |
Событие публикуется атомарно с изменением реплики (реплика + чекпоинт синка + публикация — одно
согласованное изменение, правила talent-replica.md); после фиксации —
доставляется гарантированно (at-least-once). Порядок применения geo-справочников синком
(страна → регион → город) гарантирует: к моменту публикации city-события страна и регион города уже
существуют в реплике handbook — снапшоты строятся всегда.
Payload CityUpsertedEvent
Полный snapshot реплики города на момент публикации + вложенные снапшоты родительской географии.
Служебные штампы CreatedAt/UpdatedAt в payload не входят.
| Поле | Тип | Обязательность | Источник (поле реплики) | Compatibility | Описание |
|---|---|---|---|---|---|
Id | long | Да | CityEntity.Id | Immutable | Идентификатор города = id источника (= Kafka key). |
CountryId | short | Да | CityEntity.CountryId | Additive only | Страна города. |
RegionId | long? | Нет | CityEntity.RegionId | Additive only | Регион города; null — город вне региона (напр., города федерального значения). |
Name | string | Да | CityEntity.Name | Additive only | Название города («Самара»), ≤ 256. |
Country | CountrySnapshot | Да | реплика страны на момент публикации | Additive only | Снапшот страны — поля см. snapshots.md, здесь не переописываются. |
Region | RegionSnapshot? | Нет (задан ⇔ RegionId ≠ null) | реплика региона на момент публикации | Additive only | Снапшот региона — поля см. snapshots.md. |
IsArchived | bool | Да | CityEntity.IsArchived | Additive only | Soft-архив: true — запись выведена из оборота. Tombstone-записей в топике нет — архив передаётся значением. |
ReplicaVersion | long | Да | CityEntity.ReplicaVersion | Immutable semantics | Версия записи источника; монотонный guard у потребителей (INV-HND-4). |
Снапшоты Country/Region зафиксированы на момент публикации и не «догоняются» (правило 3
snapshots.md): последующее переименование страны/региона уже опубликованные
payload-ы не меняет — актуальные имена потребитель ведёт по событиям
country-upserted / region-upserted.
Когда публикуется
| Условие | Изменение реплики | Публикация |
|---|---|---|
Применённый upsert синка Talent (handbooks.after-city-information-update), включая auto-restore заархивированной записи | создание / перезапись реплика-полей / IsArchived = false | атомарно с изменением реплики |
| Admin archive / restore | IsArchived переключён | та же |
Полная пересинхронизация (Talent POST sync-handbooks re-эмитит все строки; см. talent-initial-import) | re-emit всех записей справочника | та же |
Одно city-событие источника может породить две публикации handbook: сначала
region-upserted (если derive изменил регион), затем city-upserted — в этом порядке.
Потребители (consumers)
| Consumer-контекст | Эффект | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
chessboard (группа pin-chessboard-handbook-sync, handbook-sync) | Локальная реплика chessboard.handbook_*: валидация CityId на write-path проектов, гео-фильтры и карточки; при переименовании города — refresh денормализованных CityName затронутых проектов + re-publish project-upserted (чтобы проекции catalog догнали) | guard по ReplicaVersion (стейл — skip); повтор доставки — идемпотентный no-op | < 1 мин |
Порядок, идемпотентность и bootstrap
- Порядок per-key. Key =
Id— все события одного города строго упорядочены; потребитель держит guard поReplicaVersion(событие с версией ≤ применённой — skip + метрика stale). - Полный snapshot — замещение, не слияние. Потребитель перезаписывает свою реплику целиком; отсутствие
optional-поля (
RegionId/Region) означает «город вне региона», а не «оставить прежнее значение». - Compaction. Промежуточные версии записи могут быть вычищены компакцией — каждое событие
самодостаточно. Физических удалений и tombstone-ов нет: вывод из оборота — значением
IsArchived = true. - Bootstrap. Новый потребитель получает полный актуальный справочник чтением топика с offset 0; благодаря вложенным снапшотам display-имена страны/региона доступны сразу, не дожидаясь чтения соседних топиков.
- Порядок между топиками справочника не гарантирован: ссылки
CountryId/RegionIdдо прихода соответствующих событий могут быть нерезолвимы в реплике потребителя — display-поля на этот период берутся из вложенных снапшотов.
Доставка и повторная обработка (retry/DLQ)
| Аспект | Правило |
|---|---|
| Гарантия доставки | at-least-once; публикация атомарна с изменением реплики |
| Повтор у потребителя | повторная доставка того же события — идемпотентный no-op (version-guard) |
| Transient-ошибка обработки | retry с экспоненциальным backoff, offset не коммитится |
| Неустранимая ошибка обработки | событие — в собственный DLQ потребителя + алерт; правила — в consumer-доке потребителя (handbook-sync) |
| Отказ применения на стороне producer | событие не публикуется: неприменимое событие источника (HANDBOOK_SYNC_GEO_MISMATCH — non-retriable, HANDBOOK_SYNC_COUNTRY_NOT_FOUND — после исчерпания ретраев) ушло в pin.handbook.talent-sync-dlq.v1 (см. talent-handbook-sync), реплика и топик не изменились |
Версионирование и совместимость
- Версия — один
ContractVersion = 1; consumer при несоответствии версии события логирует warning и продолжает обработку. Топик кодирует версию суффиксом.v1. - Additive-only: новые поля (в том числе новые поля вложенных снапшотов — версионируются вместе с несущим контрактом, snapshots.md) добавляются как optional с безопасным default — без bump; неизвестные поля потребитель игнорирует.
- Breaking — новый
ContractVersion = 2+ новый топикpin.handbook.cities.v2+ dual-publish + deadline миграции consumers + sunset.v1. - Новый потребитель подключается в любой момент — bootstrap чтением compacted-топика с offset 0.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
| все поля payload (включая вложенные снапшоты) | Public (нормативный справочник) | include |
PII, secrets, tokens в событии отсутствуют — данные публичные справочные.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity: CityEntity
- Use Cases (1): UC-HND-002. Система синхронизирует реплику справочника из Talent
- События (4): Event Contract: country-upserted (CountryUpsertedEvent), Event Contract: currency-upserted (CurrencyUpsertedEvent), Event Contract: language-upserted (LanguageUpsertedEvent), Event Contract: region-upserted (RegionUpsertedEvent)
- Консюмеры (2): Consumer: Handbook Sync (pin-chessboard-handbook-sync), Consumer: Talent Handbook Sync (pin-handbook-talent-sync)
- index.md (1): Handbook Service
- registries (1): Реестр событий PIN
Связанные документы
- Сущность: city.md (дом полей, INV-HND-3/4); вложенные модели
CountrySnapshot,RegionSnapshotи модельCitySnapshotдля контрактов потребителей: snapshots.md. - Producer: talent-handbook-sync, archive-restore-entry, talent-initial-import.
- Потребитель: chessboard/consumers/handbook-sync.md.
- Соседние контракты справочника: country-upserted.md, region-upserted.md, currency-upserted.md, language-upserted.md.
- ADR-0063.