Пряма сумісність
Що таке пряма сумісність
Пряма сумісність (forward compatibility) - це здатність системи коректно працювати з новішими клієнтами або даними, ніж ті, під які вона спочатку проектувалася. Простіше: старий сервер не ламається, коли до нього приходить новий клієнт; старий споживач не падає, коли зустрічає нове повідомлення.
Від зворотної сумісності (коли нова система підтримує старих клієнтів) forward відрізняється напрямком відповідальності: ми проектуємо протоколи і клієнти так, щоб «пережити» майбутні розширення без тотального апгрейда всієї екосистеми.
Базові принципи
1. Tolerant reader & tolerant writer
Reader ігнорує невідомі поля/заголовки і допускає нові enum-значення з коректним fallback.
Writer не відправляє нічого, що сервер явно не оголосив як підтримуване (capabilities).
2. Capability negotiation
Явний обмін можливостями (фічі/версії/медіа-типи) на handshake-етапі. Клієнт адаптує свою поведінку до відповіді сервера.
3. Дефолтна деградація
Нові можливості вважаються опціональними: якщо сервер/консьюмер їх не підтримує, сценарій все одно завершиться корисним мінімумом (MGC).
4. Стабільне ядро (MGC)
Мінімальний гарантійний контракт незмінний; нововведення живуть як розширення.
5. Контракти помилок як частина протоколу
Передбачувані коди/причини («фіча не підтримується», «медіа-тип невідомий») дозволяють клієнту автоматично відкотитися на підтримуваний режим.
6. Версії без сюрпризів
Мажорні лінії відокремлені; мінорні розширення не вимагають оновлення сервера/консьюмера.
Де це особливо важливо
Публічні API з довгоживучими інтеграціями (партнери, SDK в мобільних додатках).
Подієві платформи з безліччю незалежних консьюмерів.
Мобільні клієнти, які оновлюються повільніше, ніж бекенд.
Едж/IoT, де частина парку пристроїв рідко прошивається.
Патерни реалізації за стилями
REST/HTTP
Negotiation:- 'Accept '/медіатипи з параметрами ('application/vnd. example. order+json; v=1; profile=risk`).
- `Prefer: include =...'для опціональних блоків.
- Заголовок'X-Capabilities: risk_score,item_details_v2`.
- Надсилає запит у базовому форматі, розширення - тільки якщо сервер підтвердив capability (через OPTIONS/desc/лід-ендпоінт).
- При «415/406/501» автоматично відкочується на підтримуваний формат/метод.
- Відповідь сервера: невідомі параметри - ігнорувати; зайві поля - допускаються; стабільний формат помилок ('type/code/detail/trace _ id').
gRPC / Protobuf
Стабільні сервіси: нові методи/поля - адитивно; старий сервер спокійно ігнорує невідомі поля запиту.
Feature discovery: метод'GetCapabilities ()'повертає списки фіч/лімітів. Клієнт не викликає «v2-метод», якщо сервер його не оголосив.
Стрімінг: фіксуйте порядок мінімального набору повідомлень; нові «фрейми» позначайте розширеннями/типами, які старий клієнт ігнорує.
GraphQL
Forward-friendly: нові поля/типи з'являються на сервері - старі клієнти їх просто не запитують.
Здогадки заборонені: клієнт повинен тримати схему (інтроспекція/кодоген) і не відправляти невідомі директиви/змінні.
Деградація: якщо сервер не знає кастомної директиви/feature - клієнт будує запит без неї.
Event-driven (Kafka/NATS/Pulsar, Avro/JSON/Proto)
FORWARD-сумісність схеми в реєстрі: старі консьюмери можуть читати повідомлення, записані новою схемою.
Адитивні поля з дефолтами: нові продюсери не ламають старих консьюмерів.
Core vs Enriched: ядро залишається колишнім, нові відомості публікуються в «.enriched» або як опціональні поля.
Практики проектування
1. Договір на мінімальний запит (MGC)
Операція повинна мати «вузьку шийку», яку підтримають всі сервери багато років.
2. Фіча-прапори на рівні контракту
Опишіть фічі як іменовані можливості: `risk_score`, `pricing_v2`, `strong_idempotency`. Клієнт включає їх явно.
3. Явні коди помилок для «не підтримується»
HTTP: `501 Not Implemented`, `415 Unsupported Media Type`, детальные `problem+json`.
gRPC: `UNIMPLEMENTED`/`FAILED_PRECONDITION`.
Events: маршрут в DLQ з'reason = unsupported _ feature'.
4. Не покладатися на порядок/повні списки
Клієнт повинен бути готовий до нових значень enum, відсутності нових полів і до «додаткових» властивостей.
5. Стабільні ідентифікатори та формати
Не змінюйте формат ID/ключів партіонування в рамках лінії - це ламає forward на стороні читачів.
6. Документація «машиночитаєма»
Хостіть дескриптори: OpenAPI/AsyncAPI/Proto descriptors/GraphQL SDL. Клієнти можуть звірити підтримку фіч.
Тестування forward-сумісності
Schema-diff в режимі FORWARD/FULL: нова схема валідує старого споживача/сервер.
Контрактні тести клієнта: новий клієнт виконується проти старого сервера з увімкненими/вимкненими фічами.
Golden requests: набір «нових» запитів проганяється по «старому» серверу; очікується деградація без критичних помилок.
Chaos/latency: перевірка таймаутів/ретраїв - новий клієнт повинен коректно пережити найгірші SLA старого сервера.
Canary: частина нових клієнтів працює з попередньою серверною версією - збираємо телеметрію помилок/деградацій.
Спостережуваність та операційні метрики
Частка запитів/повідомлень з непідтриманими фічами та їх автоматичними відкатами.
Розподіл за версіями клієнтів (User-Agent/метадані/claims).
Помилки'UNIMPLEMENTED/501/415'і маршрути в DLQ з'unsupported _ feature'.
Час деградації: p95/p99 для MGC проти «розширеної» відповіді.
Режими сумісності в реєстрі схем
FORWARD: новий запис сумісний зі старим читачем (потрібні дефолти, опціональність).
FULL: и FORWARD, и BACKWARD; зручно для публічних контрактів.
Рекомендація: для подій - BACKWARD у продюсера і FORWARD у консьюмера (через tolerant reader), для зовнішніх API - FULL.
Приклади
REST (capabilities + деградація)
1. Клієнт робить'GET/meta/capabilities'→'{ "risk_score": false, "price_v2": true }`.
2. На'POST/orders'відправляє базові поля;'risk _ score'не запитує, тому що сервер його не вміє.
3. Якщо випадково відправив'Prefer: include = risk _ score', сервер відповідає 200 без поля'risk _ score'( або'Preference-Applied: none') - клієнт не падає.
gRPC (discovery)
'GetCapabilities ()'повернув список методів/фіч. Клієнт не викликає'CaptureV2', якщо його немає - замість цього використовує'Capture'і локально перетворює вхідні дані до підтримуваного вигляду.
Events (FORWARD в реєстрі)
Продюсер додав поле'risk _ score'( nullable з дефолтом). Старий консьюмер його ігнорує; його логіка використовує тільки стабільні поля ядра.
Антипатерни
Жорсткий клієнт: фільтрує відповідь по whitelist-полях і падає на незнайомій властивості.
Неявні фічі: клієнт починає відправляти новий параметр без перевірки capabilities.
Зміна форматів ID/ключів в межах лінії → старі сервери/консьюмери перестають розуміти нові запити/повідомлення.
Зашиті припущення про повний список enum (switch без default).
Логування як контроль потоку: парсинг рядків помилок замість контрактних кодів.
Чек-лист впровадження
- Визначено MGC; нові можливості позначені як опціональні.
- Описаний і реалізований capability negotiation (ендпоінт/метадані/handshake).
- Клієнти ігнорують незнайомі поля і коректно обробляють нові enum (fallback).
- Контракти помилок фіксують «не підтримується» передбачувано (HTTP/gRPC/Event).
- Реєстр схем налаштований на FORWARD/FULL для відповідних артефактів.
- Автотести: schema-diff (FORWARD), контрактні тести клієнта проти старого сервера, canary.
- Метрики: версія клієнта, відмова фіч, частка деградацій, p95 MGC.
- Документація/SDK публікують список фіч і приклади деградації.
FAQ
Чим forward відрізняється від backward на практиці?
Backward: новий сервер не ламає старих клієнтів. Forward: старий сервер не ламається від нових клієнтів (або старий консьюмер - від нових повідомлень). В ідеалі ви досягаєте full.
Чи потрібно завжди вводити capabilities?
Якщо очікуєте активну еволюцію без синхронних релізів - так. Це дешевше, ніж тримати десятки major-ліній.
Як бути з безпекою?
Нові фічі повинні вимагати окремі scopes/claims. Якщо сервер їх не підтримує - клієнт не повинен знижувати безпеку, а повинен відмовитися від фічі.
Чи можна «вгадувати» підтримку за версією сервера?
Небажано. Краще питати явно (capabilities) або дивитися на медіатип/схему.
Підсумок
Пряма сумісність - це дисципліна домовлятися про можливості і безпечно деградувати. Стабільне ядро, capability negotiation, адитивні розширення і передбачувані помилки дозволяють новим клієнтам і даним уживатися зі старими серверами і споживачами - без масових релізів і нічних міграцій.