Реестр схем и эволюция данных
Зачем нужен реестр схем
Реестр схем — это централизованный источник истины для контрактов данных (API, события, потоки, сообщения, хранилища), который обеспечивает:- Предсказуемую эволюцию: правила совместимости и автоматическая проверка «ломок».
- Повторяемость и прозрачность: история версий, кто/когда/зачем менял.
- Стандартизацию: единые названия, форматы ошибок, поля трассировки, PII-метки.
- Интеграцию с CI/CD: блокировка breaking-изменений до продакшена.
Реестр связывает Protocol-first и контрактную совместимость, делая изменения быстрыми и безопасными.
Форматы и области применения
JSON Schema: REST/HTTP полезные нагрузки, документы, конфигурации.
Avro: событийные шины (Kafka/Pulsar), compact/эволюция через ID полей.
Protobuf: gRPC/RPC, бинарно-эффективен, строгие теги.
GraphQL SDL: схема типов и директив, эволюция через `@deprecated`.
SQL DDL как артефакт: фиксируем договоренные представления (например, внешние витрины) — с осторожностью.
Режимы совместимости
BACKWARD: новые схемы читают старые данные/сообщения. Подходит для продюсера, который расширяет payload аддитивно.
FORWARD: старые потребители корректно читают новые данные (требует tolerant reader).
FULL: совмещает оба (строже, удобнее для публичных контрактов).
NONE: без проверок — только для песочниц.
- Events: чаще BACKWARD (продюсер расширяет payload опционально).
- Публичные API: FULL или BACKWARD + строгий tolerant reader на клиентах.
- Внутренние прототипы: временно NONE, но не на trunk.
Безопасные (аддитивные) vs. опасные изменения
Аддитивные (ОК):- Добавление необязательного поля/типа.
- Расширение enum новыми значениями (при tolerant reader).
- Добавление альтернативной проекции/события (`.enriched`).
- Ослабление ограничений (`minLength`, `maximum` ↑, но не ↓).
- Удаление/переименование полей или изменение их типа/обязательности.
- Изменение семантики статусов/кодеков/порядка в потоках.
- Переиспользование protobuf-тегов.
- Смена ключа партиционирования в событиях.
Организация реестра
Нейминг и адресация
Группы/пространства: `payments`, `kyc`, `audit`.
Имена: `payment.authorized.v1` (events), `payments.v1.CaptureRequest` (gRPC), `orders.v1.Order` (JSON Schema).
Мажор в имени, миноры — в метаданных/версии схемы.
Метаданные
`owner` (команда), `domain`, `slas` (SLO/SLA), `security.tier` (PII/PCI), `retention`, `compatibility_mode`, `sunset`, `changelog`.
Управление жизненным циклом
Draft → Review → Approved → Released → Deprecated → Sunset.
Автоматические валидаторы/линтеры, ручной design-review (API Guild), release notes.
Интеграция в CI/CD
1. Pre-commit: локальные линтеры (Spectral/Buf/Avro tools).
2. PR-пайплайн: schema-diff → проверка compatibility mode; блокируем breaking.
3. Artifact publish: пуш согласованной схемы в реестр + генерация SDK/моделей.
4. Runtime-guard (опционально): шлюз/продюсер валидирует payload против актуальной схемы.
- `openapi-diff --fail-on-breaking`
- `buf breaking --against
` - `avro-compat --mode BACKWARD`
- генерация golden samples и прогон CDC-тестов.
Эволюция схем: практики
Additive-first: новые поля — `optional/nullable` (JSON), `optional` (proto3), default в Avro.
Модель обратной пирамиды: ядро стабильно, обогащения — рядом и опциональны.
Dual-emit/dual-write для major: параллельно публикуем `v1` и `v2`.
Sunset-план: даты, использование, предупреждения, адаптеры.
Tolerant reader: клиенты игнорируют неизвестные поля и корректно обрабатывают новые enum.
Примеры схем и проверок
JSON Schema (фрагмент, аддитивное поле)
json
{
"$id": "orders.v1.Order",
"type": "object",
"required": ["id", "status"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"status": { "type": "string", "enum": ["created", "paid", "shipped"] },
"risk_score": { "type": "number", "minimum": 0, "maximum": 1 }
},
"additionalProperties": true
}
Avro (default для совместимости)
json
{
"type": "record",
"name": "PaymentAuthorized",
"namespace": "payment.v1",
"fields": [
{ "name": "payment_id", "type": "string" },
{ "name": "amount", "type": "long" },
{ "name": "currency", "type": "string" },
{ "name": "risk_score", "type": ["null", "double"], "default": null }
]
}
Protobuf (не переиспользуйте теги)
proto syntax = "proto3";
package payments.v1;
message CaptureRequest {
string payment_id = 1;
int64 amount = 2;
string currency = 3;
optional double risk_score = 4; // additive
}
// tag=4 зарезервирован под risk_score, его нельзя менять/удалять без v2
Регистр событий и партиционирование
Именование событий: `domain.action.v{major}` (`payment.captured.v1`).
Ключ партиционирования — часть контракта (`payment_id`, `user_id`).
Core vs Enriched: `.v1` (ядро) и `.enriched.v1` (детали).
Совместимость в реестре: режимы на уровне темы/типа; CI отказывает несовместимым изменениям.
Управление миграциями
Expand → Migrate → Contract (REST/gRPC):1. добавить поля/таблицы; 2) начать писать/читать новые поля; 3) удалить старое после sunset.
- Dual-emit (Events): параллельно `v1`/`v2`, миграция консьюмеров/проекций, затем снятие `v1`.
- Replay: пересборка проекций из лога на новую схему (только при совместимости и миграторах).
- Адаптеры: гейтвей/прокси, переводящие `v1↔v2` для сложных клиентов.
Безопасность и комплаенс
PII/PCI метки в схеме: `x-pii: true`, `x-sensitivity: high`.
Политики доступа: кто может публиковать/изменять схемы (RBAC), подписывать релизы.
Криптография: подпись версий схем, неизменяемые журналы аудита (WORM).
Право на забвение: указывайте поля, требующие шифрования/крипто-стирания; guidance в реестре.
Наблюдаемость и аудит
Дашборды: количество изменений, типы (minor/major), доля отклоненных PR, использование версий.
Аудит-трейл: кто изменил схему, ссылки на PR/ADR, связанный релиз.
Runtime-метрики: процент сообщений, не прошедших валидацию; инциденты совместимости.
Инструменты (примерный стек)
OpenAPI/JSON Schema: Spectral, OpenAPI Diff, Schemathesis.
Protobuf/gRPC: Buf, buf-breaking, protoc linters.
Avro/Events: Confluent/Redpanda Schema Registry, Avro-tools, Karapace.
GraphQL: GraphQL Inspector, GraphQL Codegen.
Реестры/каталоги: Artifact Registry, Git-based registry, Backstage Catalog, custom UI.
Документация: Redocly/Stoplight, Swagger-UI, GraphiQL.
Антипаттерны
Swagger-wash: схема не отражает реальность сервиса (или наоборот).
Отключенная проверка совместимости: «надо срочно» → прод ломается.
Переиспользование protobuf-тегов: тихая порча данных.
Единый режим совместимости «для всего»: разные домены требуют разных режимов.
Сырые CDC как публичные схемы: утечка БД-модели наружу, невозможность эволюции.
Чек-лист внедрения
- Определен формат артефактов и compatibility mode по доменам.
- Настроены линтеры и schema-diff в CI, PR блокируется при breaking.
- Включен tolerant reader у клиентов; `additionalProperties=true` (где уместно).
- Мажорные изменения проходят через RFC/ADR, есть sunset-план и dual-emit/dual-write.
- Схемы помечены PII/PCI и уровнями доступа; включен аудит.
- Дашборды по использованию версий и отказам совместимости.
- Генерация SDK/моделей из реестра — часть пайплайна.
- Документация и golden samples обновлены автоматически.
FAQ
Можно ли без реестра — хранить схемы в Git?
Да, но реестр добавляет API совместимости, поиск, метаданные, централизованную политику и «on-the-fly» валидацию. Лучший вариант — Git как storage + UI/политики поверх.
Как выбирать режим совместимости?
Смотрите на направление изменений: если продюсер расширяет payload — BACKWARD. Для публичного API/SDK — FULL. Для быстрых прототипов — временно NONE (не на trunk).
Что делать при необходимости ломки?
Готовим v2: dual-emit/dual-run, sunset-даты, адаптеры, телеметрию использования, миграционные гайды.
Нужно ли валидировать payload в рантайме?
Для критичных доменов — да: это предотвращает «мусорные» сообщения и ускоряет диагностику.
Итог
Реестр схем превращает эволюцию данных из рискованной импровизации в управляемый процесс: единые правила совместимости, автоматические проверки, понятные версии и прозрачная история. Добавьте к нему дисциплину additive-first, tolerant reader, dual-emit и sunset — и ваши контракты будут развиваться быстро, без ломок и ночных инцидентов.