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

Event Contract: language-upserted (LanguageUpsertedEvent)

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

Назначение

Фиксирует каждое изменение состояния реплики языка в контексте handbook. Текущих потребителей в PIN нет — топик публикуется как задел: справочник языков реплицируется для полноты контура (страны ссылаются на язык по умолчанию через DefaultLanguageId), а compacted-хранение позволяет будущему потребителю подключиться в любой момент чтением топика с offset 0 — без доработок producer. UI-локали пользователей — строки BCP-47, дом закрытого списка — контекст profile; LanguageId для локали интерфейса не вводится (ADR-0063 п.3).

Metadata

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

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

Payload LanguageUpsertedEvent

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

ПолеТипОбязательностьИсточник (поле реплики)CompatibilityОписание
IdshortДаLanguageEntity.IdImmutableИдентификатор языка = id источника (= Kafka key).
NamestringДаLanguageEntity.NameAdditive onlyНазвание языка («Русский»), ≤ 64.
IsoCode2stringДаLanguageEntity.IsoCode2Additive onlyКод ISO 639-1 (ru), char(2), lower.
IsoCode3stringДаLanguageEntity.IsoCode3Additive onlyКод ISO 639-2/3 (rus), char(3), lower.
FlagIsoCode2string?НетLanguageEntity.FlagIsoCode2Additive onlyISO 3166-1 alpha-2 код страны для отрисовки флага языка в UI (display-атрибут), char(2), upper.
IsArchivedboolДаLanguageEntity.IsArchivedAdditive onlySoft-архив: true — запись выведена из оборота. Tombstone-записей в топике нет — архив передаётся значением.
ReplicaVersionlongДаLanguageEntity.ReplicaVersionImmutable semanticsВерсия записи источника; монотонный guard у потребителей (INV-HND-4).

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

УсловиеИзменение репликиПубликация
Применённый upsert синка Talent (handbooks.after-language-information-update), включая auto-restore заархивированной записисоздание / перезапись реплика-полей / IsArchived = falseатомарно с изменением реплики
Admin archive / restoreIsArchived переключёнта же
Полная пересинхронизация (Talent POST sync-handbooks re-эмитит все строки; см. talent-initial-import)re-emit всех записей справочниката же

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

Потребителей нет — задел

Локальная реплика chessboard подписана на четыре топика справочника, языки в её состав не входят (handbook-sync). Будущий потребитель обязан соблюдать правила раздела «Порядок, идемпотентность и bootstrap» и заводится additive: подписка + строка в этой таблице, доработок producer не требуется.

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

  • Порядок per-key. Key = Id — все события одного языка строго упорядочены; потребитель держит guard по ReplicaVersion (событие с версией ≤ применённой — skip + метрика stale).
  • Полный snapshot — замещение, не слияние. Потребитель перезаписывает свою реплику целиком; отсутствие optional-поля FlagIsoCode2 означает «значение очищено», а не «оставить прежнее».
  • Compaction. Промежуточные версии записи могут быть вычищены компакцией — каждое событие самодостаточно. Физических удалений и tombstone-ов нет: вывод из оборота — значением IsArchived = true.
  • Bootstrap. Новый потребитель получает полный актуальный справочник чтением топика с offset 0.

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

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

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

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

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

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

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

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

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

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