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 type | currency-upserted |
| Topic | pin.handbook.currencies.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).
Payload CurrencyUpsertedEvent
Полный snapshot реплики валюты на момент публикации. Служебные штампы CreatedAt/UpdatedAt в payload
не входят.
| Поле | Тип | Обязательность | Источник (поле реплики) | Compatibility | Описание |
|---|---|---|---|---|---|
Id | short | Да | CurrencyEntity.Id | Immutable | Идентификатор валюты = id источника (= Kafka key). |
Name | string | Да | CurrencyEntity.Name | Additive only | Название валюты («Российский рубль»), ≤ 64. |
IsoCode | string | Да | CurrencyEntity.IsoCode | Additive only | Код ISO-4217 (RUB), char(3), upper — display-код; в контрактах потребителей живёт только внутри CurrencySnapshot (snapshots.md). |
Sign | string? | Нет | CurrencyEntity.Sign | Additive only | Знак/символ валюты («₽»), ≤ 16. |
IsArchived | bool | Да | CurrencyEntity.IsArchived | Additive only | Soft-архив: true — запись выведена из оборота (исчезает из lookup-выдач потребителя; исторические денежные контракты со ссылкой на валюту остаются валидными). Tombstone-записей в топике нет — архив передаётся значением. |
ReplicaVersion | long | Да | CurrencyEntity.ReplicaVersion | Immutable semantics | Версия записи источника; монотонный guard у потребителей (INV-HND-4). |
Когда публикуется
| Условие | Изменение реплики | Публикация |
|---|---|---|
Применённый upsert синка Talent (handbooks.after-currency-information-update), включая auto-restore заархивированной записи | создание / перезапись реплика-полей / IsArchived = false | атомарно с изменением реплики |
| Admin archive / restore | IsArchived переключён | та же |
Полная пересинхронизация (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.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
| все поля payload | Public (нормативный справочник) | include |
PII, secrets, tokens в событии отсутствуют — данные публичные справочные.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Реплика справочников handbook в chessboard (chessboard.handbook_*), Реплика Talent Handbook: правила синка + Entity HandbookSyncState, Entity: CurrencyEntity
- Use Cases (1): UC-HND-002. Система синхронизирует реплику справочника из Talent
- События (4): Event Contract: city-upserted (CityUpsertedEvent), Event Contract: country-upserted (CountryUpsertedEvent), 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
Связанные документы
- Сущность: currency.md (дом полей, INV-HND-2/4, связка с ADR-0048);
snapshot-модель
CurrencySnapshotи правило «деньги =CurrencyId+CurrencySnapshot»: snapshots.md. - Producer: talent-handbook-sync, archive-restore-entry, talent-initial-import.
- Потребитель: chessboard/consumers/handbook-sync.md; resolve для остальных: resolve-references.md.
- Соседние контракты справочника: country-upserted.md, region-upserted.md, city-upserted.md, language-upserted.md.
- ADR-0063, ADR-0048 (RUB-only).