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

Event Contract: currency-upserted (CurrencyUpsertedEvent)

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

Назначение

Фиксирует каждое изменение состояния реплики валюты в контексте handbook: применённый upsert синка Talent, archive/restore администратора, повторную эмиссию при полной пересинхронизации. Даёт потребителям запись валюты для валидации CurrencyId на write-path денежных контрактов и display-атрибуты (код ISO-4217, название, знак). Справочник валют не означает мультивалютность — денежная арифметика PIN остаётся RUB-only (ADR-0048, currency.md). Compacted-топик служит источником начальной загрузки (чтение с offset 0).

Metadata

ПолеЗначение
Event typecurrency-upserted
Topicpin.handbook.currencies.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 CurrencyUpsertedEvent

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

ПолеТипОбязательностьИсточник (поле реплики)CompatibilityОписание
IdshortДаCurrencyEntity.IdImmutableИдентификатор валюты = id источника (= Kafka key).
NamestringДаCurrencyEntity.NameAdditive onlyНазвание валюты («Российский рубль»), ≤ 64.
IsoCodestringДаCurrencyEntity.IsoCodeAdditive onlyКод ISO-4217 (RUB), char(3), upper — display-код; в контрактах потребителей живёт только внутри CurrencySnapshot (snapshots.md).
Signstring?НетCurrencyEntity.SignAdditive onlyЗнак/символ валюты («₽»), ≤ 16.
IsArchivedboolДаCurrencyEntity.IsArchivedAdditive onlySoft-архив: true — запись выведена из оборота (исчезает из lookup-выдач потребителя; исторические денежные контракты со ссылкой на валюту остаются валидными). Tombstone-записей в топике нет — архив передаётся значением.
ReplicaVersionlongДаCurrencyEntity.ReplicaVersionImmutable semanticsВерсия записи источника; монотонный guard у потребителей (INV-HND-4).

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

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

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

Consumer-контекстЭффектИдемпотентностьЗадержка (SLA)
chessboard (группа pin-chessboard-handbook-sync, handbook-sync)Локальная реплика chessboard.handbook_*: валидация CurrencyId на write-path проектов и цен объектов, display-атрибуты для CurrencySnapshot в исходящих событияхguard по ReplicaVersion (стейл — skip); повтор доставки — идемпотентный no-op< 1 мин
Кто не подписан

Сервисы crm/rewards/investor-owner/promotion/messenger на топик не подписываются — они получают CurrencyId + CurrencySnapshot из событий соседних сервисов, а валидацию на admin-write делают синхронным internal resolve (resolve-references, стратегия потребления — ADR-0063 п.6).

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

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

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

АспектПравило
Гарантия доставкиat-least-once; публикация атомарна с изменением реплики
Повтор у потребителяповторная доставка того же события — идемпотентный no-op (version-guard)
Transient-ошибка обработкиretry с экспоненциальным backoff, offset не коммитится
Неустранимая ошибка обработкисобытие — в собственный DLQ потребителя + алерт; правила — в consumer-доке потребителя (handbook-sync)
Отказ применения на стороне 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.currencies.v2 + dual-publish + deadline миграции consumers + sunset .v1.
  • Новый потребитель подключается в любой момент — bootstrap чтением compacted-топика с offset 0.

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

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

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

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

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

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