Реєстр схем та еволюція даних
Навіщо потрібен реєстр схем
Реєстр схем - це централізоване джерело істини для контрактів даних (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 - і ваші контракти будуть розвиватися швидко, без ломок і нічних інцидентів.