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

Event Contract: region-upserted (RegionUpsertedEvent)

Версионируемый контракт интеграционного события «запись региона справочника создана/изменена». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей — entity: RegionEntity.

Назначение

Фиксирует каждое изменение состояния реплики региона в контексте handbook. Регион в PIN — первоклассная запись справочника со своим топиком, хотя у источника (Talent Handbook) собственного region-события нет: строки регионов derive-ятся синком из вложенной модели RegionEventModel city-событий (ADR-0063 п.4). Событие даёт downstream-сервисам ключ гео-фильтров (RegionId) и актуальное имя региона без синхронного запроса в handbook; compacted-топик служит источником начальной загрузки (чтение с offset 0).

Metadata

ПолеЗначение
Event typeregion-upserted
Topicpin.handbook.regions.v1compacted (cleanup.policy = compact); последняя запись каждого региона хранится бессрочно
Partitions / key1 (сотни строк, строгий порядок) / key = Id записи
ContractVersion1
Producerhandbook: derive из city-событий синка (talent-handbook-sync), admin archive/restore (archive-restore-entry), полная пересинхронизация (talent-initial-import)
Статусdraft

Событие публикуется атомарно с изменением реплики (реплика + чекпоинт синка + публикация — одно согласованное изменение, правила talent-replica.md); после фиксации — доставляется гарантированно (at-least-once).

Payload RegionUpsertedEvent

Полный snapshot реплики региона на момент публикации. Служебные штампы CreatedAt/UpdatedAt в payload не входят.

ПолеТипОбязательностьИсточник (поле реплики)CompatibilityОписание
IdlongДаRegionEntity.IdImmutableИдентификатор региона = id CountryRegion источника (= Kafka key).
CountryIdshortДаRegionEntity.CountryIdImmutable semanticsСтрана региона; перенос региона между странами источником не поддерживается (INV-HND-3, region.md).
NamestringДаRegionEntity.NameAdditive onlyНазвание региона («Самарская область»), ≤ 256.
IsArchivedboolДаRegionEntity.IsArchivedAdditive onlySoft-архив: true — запись выведена из оборота. Tombstone-записей в топике нет — архив передаётся значением.
LastEventAtDateTimeOffsetДаRegionEntity.LastEventAtImmutable semanticsВремя последнего применённого события источника (OccurredAt city-события). Играет роль версии: у источника нет собственного версионирования региона, монотонный guard потребителя — time-based (событие с LastEventAt ≤ применённого — skip, INV-HND-4).

Когда публикуется

УсловиеИзменение репликиПубликация
Derive из city-события синка Talent — только при фактическом изменении записи (создание, смена Name, auto-restore); city-событие, не изменившее регион, публикацию не порождаетсоздание / обновление Name+LastEventAt / IsArchived = falseатомарно с изменением реплики
Admin archive / restoreIsArchived переключёнта же
Полная пересинхронизация (re-emit всех строк источника; см. talent-initial-import)re-emit всех записей справочниката же

Известное ограничение источника: переименование региона доезжает до PIN только вместе с city-событием любого из его городов; между city-событиями имя в реплике (и в топике) может устаревать — гарантированно закрывается полной пересинхронизацией (region.md, особенности наполнения).

Потребители (consumers)

Consumer-контекстЭффектИдемпотентностьЗадержка (SLA)
chessboard (группа pin-chessboard-handbook-sync, handbook-sync)Локальная реплика chessboard.handbook_*: валидация RegionId на write-path проектов, гео-фильтры; при переименовании региона — refresh денормализованных RegionName затронутых проектов + re-publish project-upserted (чтобы проекции catalog догнали)guard по LastEventAt (стейл — skip); повтор доставки — идемпотентный no-op< 1 мин

Порядок, идемпотентность и bootstrap

  • Порядок per-key. Key = Id — все события одного региона строго упорядочены; потребитель держит time-based guard по LastEventAt (событие со временем ≤ применённого — skip + метрика stale).
  • Полный snapshot — замещение, не слияние. Потребитель перезаписывает свою реплику целиком.
  • Compaction. Промежуточные состояния записи могут быть вычищены компакцией — каждое событие самодостаточно. Физических удалений и tombstone-ов нет: вывод из оборота — значением IsArchived = true.
  • Bootstrap. Новый потребитель получает полный актуальный справочник чтением топика с offset 0; re-emit при пересинхронизации для уже подписанных — идемпотентный no-op.
  • Порядок между топиками справочника не гарантирован: событие региона может прийти потребителю раньше события своей страны — ссылка CountryId до прихода страны допускается нерезолвимой (потребитель ретраит/дозаполняет display-поля страны по мере прихода pin.handbook.countries.v1).

Доставка и повторная обработка (retry/DLQ)

АспектПравило
Гарантия доставкиat-least-once; публикация атомарна с изменением реплики
Повтор у потребителяповторная доставка того же события — идемпотентный no-op (time-based guard)
Transient-ошибка обработкиretry с экспоненциальным backoff, offset не коммитится
Неустранимая ошибка обработкисобытие — в собственный DLQ потребителя + алерт; правила — в consumer-доке потребителя (handbook-sync)
Отказ применения на стороне producerсобытие не публикуется: неприменимое city-событие источника (HANDBOOK_SYNC_GEO_MISMATCH) ушло в pin.handbook.talent-sync-dlq.v1 (см. talent-handbook-sync), реплика и топик не изменились

Версионирование и совместимость

  • Версия — один ContractVersion = 1; consumer при несоответствии версии события логирует warning и продолжает обработку. Топик кодирует версию суффиксом .v1.
  • Additive-only: новые поля добавляются как optional с безопасным default — без bump; неизвестные поля потребитель игнорирует.
  • Breaking — новый ContractVersion = 2 + новый топик pin.handbook.regions.v2 + dual-publish + deadline миграции consumers + sunset .v1.
  • Новый потребитель подключается в любой момент — bootstrap чтением compacted-топика с offset 0.

Безопасность

ПолеКласс данныхПравило
все поля payloadPublic (нормативный справочник)include

PII, secrets, tokens в событии отсутствуют — данные публичные справочные.

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

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

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