Версіонування API і контрактна сумісність
TL; DR
Сумісність - це дисципліна, а не удача. Тримайте чітку політику версій (SemVer), математику змін (що «ламає», що ні), контрактні тести, регістри схем і Sunset-процедури. Для грошей і комплаєнсу - строгий REST/gRPC з vN, для UI-агрегацій - еволюційний GraphQL з'@deprecated'. Завжди: канарний трафік, зворотна сумісність ≥ один релізний цикл, міграційні гайди, телеметрія використання полів.
1) Базові поняття та цілі
Backwards-compatible (BC): новому серверу підходять старі клієнти.
Forwards-compatible (FC): старому серверу підходять нові клієнти (обмежено).
Wire-сумісність: формат на «дроті» не ламається (особливо важливо для gRPC/Protobuf).
SemVer: `MAJOR. MINOR. PATCH'- ламаєте контракт → підвищуєте MAJOR.
Мета: мінімізувати ламаючі зміни і забезпечувати передбачувані вікна міграції.
2) Матриця змін: Що можна, а що не можна
3) Політики для різних стилів API
3. 1 REST
Версія в URI ('/v1/...') або в домені ('api-v1.'). Заголовна версія - тільки для внутрішніх випадків.
Додавайте, не видаляйте: нові поля - ок, старі - позначайте як'deprecated'в схемі і залишайте мінімум на один цикл.
Статуси/помилки: не змінюйте коди і структуру'error. code/error. message/error. details`.
Ідемпотентність незмінна: не перетворюйте безпечний'POST'з'Idempotency-Key'в «поведінково інший» виклик.
3. 2 gRPC / Protobuf
Номери полів - священні: не перевикориставайте видалені номери, позначайте як «reserved».
Тільки додавання нових optional/репіт полів; «жорсткі обов'язкові» - через валідацію, не'required'.
Версійні пакети: `payments. v1`, `payments. v2`.
Сумісність сервісів: нові RPC → новий метод; поведінка старих не змінюємо.
proto message Payout {
reserved 4 ;//field deleted, number reserved string id = 1;
string currency = 2;
int64 amount_minor = 3;
// v2: optional string comment = 5;
}
3. 3 GraphQL
Еволюція без v2: додавайте поля/типи; видалення - через'@deprecated (reason)'з анонсом вікна.
Persisted Queries: для публічних клієнтів використовуйте білий список запитів - легше контролювати сумісність.
Field-level authZ і телеметрія: знайте, які поля реально використовуються, перш ніж видаляти.
graphql type Payout {
id: ID!
amountMinor: Long!
currency: String!
comment: String @deprecated(reason: "Use note")
note: String
}
3. 4 Вебхукі
Версія в дорозі ('/webhooks/v1/payments') і фіксований конверт події ('event _ id','type','ts','data').
Підписи/НМАС збережіть незмінними; нові алгоритми - як опція з прапором.
Розширення - тільки через нові поля'data.'і'headers'- без видалення старих.
4) API Gateway і маршрутизація версій
Rules-based routing: по префіксу '/v1', по заголовку'X-Api-Version', по домену.
Shadow/Canary: відображайте частину продакшн-трафіку на нову версію «в тінь», порівнюйте відповіді.
Rate/Quotas per-version: захищає старі клієнти під час міграції.
- `Sunset:
'- дата вимкнення версії - `Deprecation: true'- версія застаріває
- `Link:
; rel = «deprecation»'- на changelog/гайд міграції
nginx location ~ ^/v2/ {
proxy_pass http://api_v2;
}
location ~ ^/v1/ {
add_header Deprecation "true";
add_header Sunset "Thu, 01 May 2026 00:00:00 GMT";
proxy_pass http://api_v1;
}
5) Регістри схем і контракти
OpenAPI / JSON Schema для REST; Protobuf descriptors для gRPC; SDL registry для GraphQL.
CI-перевірки: linters + «breaking-changes check» при PR.
Consumer-Driven Contracts (CDC): тести споживачів (Pact/аналог) - захист від непомітних ламань.
Changelog: machine-readable (наприклад,'CHANGELOG. md'+ реліз-ноти в реєстрі).
6) Еволюція полів: Практичні правила
ID/ключі: не змінюйте формат (UUID↔int) без нового поля'_ v2'і перехідного періоду.
Час/валюта: тримайте UTC ISO-8601/epoch і amount_minor + currency; не змінюйте масштаб (копійки/центи).
Enum: додавайте значення - ок; не змінюйте сенс старих. Для REST - віддавайте string-значення, а не ints.
Пагінація: cursor-based стабільніше; Не змінюйте семантику курсора.
7) Деприкація і «Sunset» -процедура
1. Анонс (T-90/60): changelog, розсилка партнерам, заголовки'Deprecation/Sunset'.
2. Дублюючий період: V1 і V2 працюють паралельно; V1 забезпечений попередженнями у відповідях/логах.
3. Телеметрія використання: хто ще кличе V1? Точкові контакти.
4. Заморожування V1: тільки багфікси/без фіч.
5. Вимкнення (Sunset): 410 Gone або блок-сторінка з інструкцією міграції.
8) Релізи без болю: стратегії викладки
Blue/Green или Weighted routing: 1-5-25-50-100% трафіку.
Compatibility window: мінімум 1-2 мінорних релізи, частіше 6-12 місяців для зовнішніх API.
Feature Flags: щоб включати нові поля/гілки логіки без зміни версії.
Read/Write-розщеплення: спочатку додайте підтримку читання нового поля, потім почніть писати його.
9) Тестування сумісності (пакет практик)
Golden-тести на відповіді старих версій.
Diff-тести схем: заборона breaking в CI.
Replay продакшн-трас на staging для V2 (shadow).
Back/Forward сценарії: новий клієнт на старому сервері і навпаки (там, де FC припустимо).
Контрактні тести вебхуків: перевірка підпису, формату, часу.
10) Метрики і SLO процесу версіонування
% клієнтів на останній MINOR (мета ≥ 80% перед Sunset).
Помилки сумісності/недоступності на випуск (мета - 0).
Частка викликів застарілої версії (спадає до Sunset).
Час міграції клієнта (медіана/п95).
Latency/regression delta між версіями (не гірше базової).
11) Приклади артефактів
OpenAPI (фрагмент, деприкація поля):yaml components:
schemas:
Payout:
type: object properties:
id: { type: string, format: uuid }
amount_minor: { type: integer }
currency: { type: string }
comment:
type: string deprecated: true description: "Use note"
note: { type: string }
Protobuf (reserved і v2 пакет):
proto syntax = "proto3";
package payouts. v1;
message Payout { reserved 5; string id=1; int64 amount_minor=2; string currency=3; }
GraphQL (деприкація):
graphql type Query { payout(id: ID!): Payout }
12) Версіонування суміжних каналів
SDK/CLI: SemVer + залежність від API-версії, сумісність обумовлена в README.
Події/стріми (WS/Kafka): версія в'envelope. version`; нові атрибути - опціональні; дедуп і резюм працюють однаково між версіями.
Звітність/CSV: версія в імені файлу/шапці; додавайте стовпці праворуч; не змінюйте порядок/типи.
13) Governance і ролі
Власник контракту (domain owner), API Steward (правила і лінтери), Release Manager (Sunset/комунікації).
RFC-процес для breaking-змін: бізнес-обґрунтування, план міграції, артефакти, дати.
Єдиний каталог API: де видно схеми, версії, Sunset-календар, контакт.
14) Анти-патерни
«Тихі» ламання: змінюємо статус/помилку/тип поля без версії.
Перевикористання protobuf-номерів - руйнує реплей і старі клієнти.
GraphQL без телеметрії використання полів - видалення «навпомацки».
Глобальна v2 всього - мегаміграція замість точкової еволюції.
Версія в query-параметрі для публічного API - неочевидна і вразлива схема.
Немає міграційних гайдів і прикладів - партнери буксують, терміни зриваються.
15) Check-list релізу нової версії
- Оновлена схема (OpenAPI/Protobuf/SDL), пройдені лінтери і breaking-checks.
- Додані інтеграційні та контрактні тести (CDC).
- Готові SDK/приклад коду/міграційний гайд і Changelog.
- Включені'Deprecation/Sunset'( для старої версії) + сторінка «How to migrate».
- Canary/Shadow план, алерти і дашборди порівняння метрик.
- Зворотна сумісність збережена на узгоджений термін.
- План відкату (rollback) і матриця ризиків узгоджені.
Резюме
Стабільне API - це процес, а не «раз і назавжди». Живіть за правилами: SemVer + add-only еволюція + регістр схем + контрактні тести + Sunset-процедури. Розділяйте стилі (REST/gRPC/GraphQL) та їх політики, маршрутизуйте версії на API Gateway, викочуйте канарками, вимірюйте ефект. Так ви уникнете «ламаючих сюрпризів», прискорите інтеграції партнерів і збережете передбачуваність для грошових і комплаєнс-критичних доменів.