Files
mdm-ordinis/docs/integration/api/events.md
T
Zimin A.N. bcfb07f547 docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI
Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
2026-05-07 23:27:03 +03:00

6.7 KiB
Raw Blame History

События (Kafka outbox)

Ordinis публикует все изменения справочников в Kafka через outbox pattern (at-least-once гарантия). Consumers подписываются на топики и инвалидируют свой кэш / обновляют projection.

Топики

Топик Scope Назначение
ordinis.cuod.events.public PUBLIC Изменения публичных справочников.
ordinis.cuod.events.internal INTERNAL INTERNAL словари — нужна role ordinis-internal+.
ordinis.cuod.events.restricted RESTRICTED RESTRICTED — role ordinis-restricted.

Параметры топиков (для consumer config):

  • cleanup.policy: delete
  • retention.ms: 604800000 (7 дней)
  • Multi-partition (точное число — спросить Ordinis team при registration, partition count может меняться по operational reasons).

Подключение

Bootstrap address + creds

Запросить у Ordinis team:

  • Bootstrap servers (внутренний DNS на per-environment basis)
  • SASL credentials (SCRAM-SHA-512 username + password)
  • Topic ACL (Read + Describe на нужные топики)
  • Consumer group prefix (для group ACL)

Auth: SASL/SCRAM-SHA-512. TLS опциональный (отдельный listener).

Креденшелы выдаются per-consumer service. Не share между сервисами — ACL и audit за каждым.

Schema события

dictionary.changed

Все события имеют общий envelope:

{
  "schemaVersion": "1.0.0",
  "eventType": "dictionary.changed",
  "occurredAt": "2026-05-07T15:23:45.123+03:00",
  "txTime": "2026-05-07T15:23:45.100+03:00",
  "changedBy": "admin@nstart.space",
  "changeReason": "Обновление after launch",
  "scope": "PUBLIC",
  "dictionary": "spacecraft",
  "businessKey": "RESURS-P-3",
  "action": "updated",
  "version": 3,
  "validFrom": "2026-05-07T00:00:00+03:00",
  "validTo": "9999-12-31T23:59:59+03:00",
  "data": {
    "name": { "ru-RU": "Ресурс-П № 3 (обновлён)" },
    "status": "active",
    "satelliteTypeCode": "EARTH-OBSERVATION-OPTICAL"
  },
  "previousData": {
    "name": { "ru-RU": "Ресурс-П № 3" },
    "status": "in-orbit"
  }
}

Поля envelope

Поле Описание
schemaVersion Semver схемы события. Bump major = breaking change.
eventType dictionary.changed для CRUD, dictionary.bulk_imported для bundle imports.
occurredAt Wall-clock когда событие создано.
txTime Transaction time изменения в БД (для ordering).
action created / updated / closed.
version Optimistic lock version. Monotonic per businessKey.
data Payload новой версии.
previousData Diff-like — поля, которые поменялись (только для updated). Полезен для consumer'ов которые хотят минимальный set re-fetches.
validFrom / validTo Bitemporal границы.

action semantics

  • created — новая запись с уникальным businessKey. previousData = null.
  • updated — существующий businessKey, новая версия. previousData = изменённые поля старой версии.
  • closedvalidTo обновлён в now() (раньше 9999-...). Запись больше не возвращается на ?at=now. previousData = вся старая версия.

Cascade events (v2 roadmap)

При cascade close (см. design doc dict-relationships-v2 — внутренний artifact в ~/.gstack/projects/claude/) на каждую закрытую запись отдельное событие emit'ится в outbox с дополнительным полем cascadeSource = {dictionary, businessKey} источника. Consumer может trace cascade chain.

Partition key

Сообщения партиционируются по dictionary + businessKey (одна запись — одна partition). Это даёт consumer'ам ordering guarantee per record.

key = "spacecraft:RESURS-P-3"

{% note info %}

Если consumer обрабатывает события параллельно (multiple consumer threads / pods), Kafka гарантирует только partition-level ordering. Ordering между records в разных partitions не определён. Для consumer'ов это норма — each businessKey изолирован.

{% endnote %}

At-least-once семантика

Ordinis использует outbox pattern — изменение БД и публикация в Kafka атомарны через transactional outbox. Гарантия: at-least-once.

Что это значит для consumer'а:

  • Можешь получить дубликат события (rare, при partial failures на стороне Ordinis). Idempotency у consumer'а — must.
  • При недоступности Kafka события накапливаются в Ordinis side и доставляются после восстановления — events не теряются.
  • Order — гарантирован per partition key (dictionary:businessKey), не глобально.

Idempotency

Используй (dictionary, businessKey, txTime, version) как dedup key:

seen = redis.get(f"ordinis:event:{dict}:{key}:{tx_time}:{ver}")
if not seen:
    process_event(event)
    redis.setex(f"ordinis:event:...", 86400, "1")

Persistent failures (Ordinis side)

Если событие не публикуется > 1000 retry попыток (multi-day Kafka outage), оно складывается в DLQ внутри Ordinis. Не теряется, но требует ручного разбора Ordinis team. Consumer не видит таких событий до их re-publishing — но это очень редкий сценарий.

Топик для discovery

В roadmap планируется nsi.dictionary.changed aggregator topic — single firehose для consumers которые хотят видеть все scope'ы в одном потоке (c фильтрацией consumer-side). Сейчас нет, подписывайся на 3 scope топика отдельно.

Дальше

  • Webhooks — alternative для consumer'ов без Kafka access
  • Bitemporal — как интерпретировать validFrom/validTo
  • Caching — invalidation pattern с Kafka events