API Versioning: эволюция API без боли
Зачем знать на Middle 3: API без версионирования — это API без будущего. Tech lead обязан понимать: как ввести
/v2/, чтобы не сломать/v1/; как эволюционировать protobuf без breaking changes; зачем нужен Schema Registry для Kafka; как Buf принуждает к compatibility. Без этих знаний один деплой ломает 50 клиентов и месяц refund’ов. С ними — годами поддерживаешь старых клиентов и плавно мигрируешь.
Содержание
Заголовок раздела «Содержание»- Концепция: зачем версионировать API
- Глубже: стратегии, protobuf/JSON evolution, Schema Registry, Buf
- Gotchas: типовые ошибки эволюции
- Real cases: Stripe, GitHub, gRPC миграции
- Вопросы (20+)
- Practice: эволюция API без breaking
- Источники
1. Концепция
Заголовок раздела «1. Концепция»1.1 Почему API versioning сложнее, чем кажется
Заголовок раздела «1.1 Почему API versioning сложнее, чем кажется»Today: запускаем /api/users → {id, name, email}3 месяца: 50 клиентов интегрировались6 месяцев: нужно поле age → как добавить, не сломав?12 месяцев: нужно убрать email (GDPR) → как?18 месяцев: пишем v2 → как мигрировать 50 клиентов?Реальность:
- У клиентов разные релизные циклы (mobile app — годы).
- Деплой нельзя «отменить» — клиент уже скачал.
- Backward compatibility — не «фича», а обязательство.
1.2 Compatibility — две стороны
Заголовок раздела «1.2 Compatibility — две стороны»Backward compatibility — новая версия сервера понимает старых клиентов. Forward compatibility — старая версия клиента понимает новый ответ сервера.
Server v2 ←→ Client v1 (backward)Server v1 ←→ Client v2 (forward)Хочется обе, тогда можно деплоить server и client независимо.
1.3 Что такое breaking change
Заголовок раздела «1.3 Что такое breaking change»✅ Не breaking:
- Добавить optional поле в response.
- Добавить новый endpoint.
- Добавить enum value (если клиенты толерантны).
- Расширить диапазон допустимых значений.
❌ Breaking:
- Убрать поле из response.
- Изменить тип поля (string → int).
- Сделать optional поле required.
- Изменить семантику (тот же ключ, другой смысл).
- Сузить диапазон.
2. Глубже
Заголовок раздела «2. Глубже»2.1 Стратегии версионирования
Заголовок раздела «2.1 Стратегии версионирования»1. URL path (самый популярный)
GET /api/v1/users/123GET /api/v2/users/123Pros:
- Видно в URL, легко логировать.
- Просто роутить (gateway).
- Кеши прозрачны.
Cons:
- Семантически не RESTful (URI должен быть «ресурс», не версия).
- Дублирование кода / endpoints.
2. Header (Accept-Version / custom)
GET /api/users/123Accept-Version: 2или:
GET /api/users/123X-API-Version: 2024-08-15Pros:
- URL остаётся «чистым».
- Гибко: можно versionать по датам (Stripe).
Cons:
- Не видно «глазами» в URL.
- Сложнее с кешами.
- Часто забывают.
3. Content-Type (vendor MIME types)
GET /api/users/123Accept: application/vnd.myapi.v2+jsonPros:
- HTTP-стандарт (content negotiation).
- Самый RESTful.
Cons:
- Уродливо в коде клиента.
- Многословно.
4. Subdomain
api.v2.example.com/users/123Pros:
- Можно по-разному масштабировать.
Cons:
- Дополнительный DNS / cert.
- Сложно для клиентов.
5. Query parameter (worst)
GET /api/users/123?version=2Cons:
- Хрупкий: легко забыть.
- Кеши.
2.2 Сравнение стратегий
Заголовок раздела «2.2 Сравнение стратегий»| Стратегия | Простота | RESTful | Cache-friendly | Популярность |
|---|---|---|---|---|
| URL path | ⭐⭐⭐ | ⭐ | ⭐⭐⭐ | Очень |
| Header | ⭐⭐ | ⭐⭐ | ⭐⭐ | Средне |
| Content-Type | ⭐ | ⭐⭐⭐ | ⭐⭐ | Редко |
| Subdomain | ⭐⭐ | ⭐ | ⭐⭐⭐ | Редко |
| Query | ⭐⭐⭐ | ⭐ | ⭐ | Анти-паттерн |
Stripe использует header с date-based версией (Stripe-Version: 2024-08-15). Это «дата API» — позволяет fine-grained эволюцию.
2.3 Backward compatibility техники
Заголовок раздела «2.3 Backward compatibility техники»1. Tolerant reader pattern
Клиент игнорирует неизвестные поля:
type User struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` // не описываем новые поля — JSON decoder их пропускает}Это forward compatibility клиента.
2. Defaults для отсутствующих полей
type User struct { ID string `json:"id"` Role string `json:"role,omitempty"` // default = ""}
func (u *User) GetRole() string { if u.Role == "" { return "user" // default } return u.Role}3. Field deprecation
Не удаляй сразу — пометь:
type User struct { ID string `json:"id"` Email string `json:"email"` // current Mail string `json:"mail,omitempty"` // DEPRECATED: use Email}OpenAPI:
properties: mail: type: string deprecated: true description: "DEPRECATED. Use email."4. New endpoint instead of breaking change
Вместо:
GET /users/123 → раньше возвращал {name}, теперь {firstName, lastName}Лучше:
GET /v1/users/123 → {name} (старый)GET /v2/users/123 → {firstName, lastName} (новый)2.4 Protobuf evolution rules
Заголовок раздела «2.4 Protobuf evolution rules»Protobuf изначально проектировался для эволюции схем.
Правила:
✅ Безопасно:
- Добавить новое поле с новым номером — старые читатели игнорируют.
- Удалить поле — но
reservedдля номера:message User {string id = 1;string name = 2;reserved 3; // нельзя переиспользоватьreserved "old_field"; // и имя} - Изменить optional → required в proto3 (там всё optional с дефолтами).
- Совместимые типы:
int32 ↔ uint32 ↔ int64 ↔ uint64 ↔ bool(с потерей precision если range).sint32 ↔ sint64.bytes ↔ string(если valid UTF-8).
- Добавить enum value — старые читатели увидят
UNKNOWN. - Расширить oneof добавлением полей в него.
❌ Опасно:
- Изменить номер поля — старые клиенты будут читать другое поле.
- Изменить тип несовместимо (
string ↔ int32— нет). - Переименовать поле — wire format ок (по номерам), но JSON/code generators ломаются.
- Сменить cardinality singular → repeated → breaking в wire format.
Пример эволюции:
// v1message Order { string id = 1; double total = 2;}
// v2 — backward compatiblemessage Order { string id = 1; double total = 2; string currency = 3; // new field reserved 4; // зарезервировали на будущее repeated Item items = 5; // new (старые клиенты не увидят items)}
message Item { string sku = 1; int32 quantity = 2;}2.5 JSON evolution rules
Заголовок раздела «2.5 JSON evolution rules»JSON не имеет встроенной schema → правила менее формальные, но похожие.
✅ Безопасно:
- Добавить optional поле (с default или omitempty).
- Принимать дополнительные поля.
❌ Breaking:
- Удалить поле, которое клиенты читают.
- Изменить тип (
{"age": 25}→{"age": "25"}). - Сделать optional → required.
- Изменить семантику.
Go tolerant reader:
type User struct { ID string `json:"id"` Name string `json:"name"` // Не объявленные поля — игнорируются json.Unmarshal по умолчанию.}
// СТРОГИЙ режим:dec := json.NewDecoder(r)dec.DisallowUnknownFields() // упадёт на новых полях — НЕ ИСПОЛЬЗОВАТЬBest practice: никогда не использовать DisallowUnknownFields в production-клиентах.
2.6 gRPC versioning
Заголовок раздела «2.6 gRPC versioning»Подход 1: версия в protobuf package
syntax = "proto3";package api.users.v1;
service UserService { rpc GetUser(GetUserRequest) returns (User);}package api.users.v2;
service UserService { rpc GetUser(GetUserRequest) returns (User); rpc GetUserProfile(GetUserRequest) returns (UserProfile); // new}Регистрируются как разные сервисы: api.users.v1.UserService, api.users.v2.UserService.
Подход 2: deprecation внутри одного service
service UserService { rpc GetUser(GetUserRequest) returns (User) { option deprecated = true; // mark old method } rpc GetUserV2(GetUserRequest) returns (User);}Подход 3: эволюция request/response
Добавлять поля по правилам proto, не менять методы.
2.7 GraphQL versioning
Заголовок раздела «2.7 GraphQL versioning»GraphQL философия: no versions in URL. Эволюция через deprecation.
type User { id: ID! name: String! email: String! @deprecated(reason: "Use `contactInfo.email`") contactInfo: ContactInfo!}
type ContactInfo { email: String! phone: String}Deprecation lifecycle:
- Add new field
contactInfo. - Mark old
emailas@deprecated. - Monitor usage (через analytics, например, GraphQL Hive).
- После N месяцев — drop с announcements.
Преимущества:
- Один endpoint.
- Клиент сам выбирает поля (overlong response не проблема).
Сложности:
- Schema breaking changes возможны (изменение типа аргумента).
- Нужны tools для контроля (GraphQL Inspector, Apollo Studio).
2.8 Schema Registry для Kafka
Заголовок раздела «2.8 Schema Registry для Kafka»Зачем: при сериализации событий (Avro/Protobuf/JSON Schema) обе стороны должны знать схему. Schema Registry — центральное хранилище.
Confluent Schema Registry (de facto):
- REST API.
- Хранит схемы по subjects (обычно
<topic>-valueи<topic>-key). - Каждая схема — версия.
- Compatibility check при регистрации.
Альтернативы:
- Karapace (Aiven) — open-source Confluent SR replacement.
- Apicurio Registry (Red Hat) — поддерживает Avro/Proto/JSON Schema/OpenAPI/AsyncAPI.
- Buf Schema Registry (BSR) — для protobuf.
2.9 Compatibility modes в Schema Registry
Заголовок раздела «2.9 Compatibility modes в Schema Registry»| Mode | Описание | Старая схема может читать новые данные? | Новая схема может читать старые данные? |
|---|---|---|---|
| BACKWARD | Default | Да (новые consumers ↔ старые данные) | — |
| BACKWARD_TRANSITIVE | Со всеми предыдущими версиями | Да | — |
| FORWARD | — | Да | |
| FORWARD_TRANSITIVE | — | Да | |
| FULL | Both | Да | Да |
| FULL_TRANSITIVE | Both со всеми версиями | Да | Да |
| NONE | No checks | — | — |
Чаще всего: BACKWARD (новые consumers могут читать любые старые сообщения).
2.10 Avro evolution в Schema Registry
Заголовок раздела «2.10 Avro evolution в Schema Registry»Avro schema:
{ "type": "record", "name": "Order", "fields": [ {"name": "id", "type": "string"}, {"name": "total", "type": "double"}, {"name": "currency", "type": "string", "default": "USD"} ]}Backward compatibility правила (Avro):
- Можно добавить поле с
default. - Можно удалить поле, если оно имеет
defaultв новой схеме (sic). - Тип можно promotion:
int → long → float → double. - Union с null:
["null", "string"]для optional.
2.11 Schema Registry в Go
Заголовок раздела «2.11 Schema Registry в Go»import ( "github.com/riferrei/srclient")
client := srclient.CreateSchemaRegistryClient("http://localhost:8081")
// Register schema (or get existing)schema, err := client.CreateSchema("orders-value", schemaStr, srclient.Avro)if err != nil { panic(err) }
// При продюсе:// magic byte (0x00) + schema_id (4 bytes) + Avro payloadheader := make([]byte, 5)header[0] = 0x00binary.BigEndian.PutUint32(header[1:5], uint32(schema.ID()))
avroBytes, _ := avroCodec.BinaryFromNative(nil, record)msg := append(header, avroBytes...)producer.Produce(msg)2.12 Subject naming strategies
Заголовок раздела «2.12 Subject naming strategies»| Strategy | Naming | Когда |
|---|---|---|
| TopicName (default) | <topic>-value / <topic>-key | Одна schema per topic |
| RecordName | <full.record.Name> | Одна schema повторяется в разных topics |
| TopicRecordName | <topic>-<full.Record> | Несколько schemas в одном topic |
2.13 Buf — современный protobuf tooling
Заголовок раздела «2.13 Buf — современный protobuf tooling»Buf (buf.build) — инструмент Lyft/Buf inc. для protobuf:
buf lint— линтер (best practices).buf breaking— детектит breaking changes.buf generate— генерация stubs.buf build— сборка в*.binimage.- BSR (Buf Schema Registry) — managed registry.
buf.yaml:
version: v2modules: - path: protodeps: - buf.build/googleapis/googleapisbreaking: use: - FILElint: use: - DEFAULTbuf.gen.yaml:
version: v2plugins: - remote: buf.build/protocolbuffers/go out: gen/go opt: paths=source_relative - remote: buf.build/grpc/go out: gen/go opt: paths=source_relativeBreaking detection в CI:
buf breaking --against 'https://github.com/myorg/proto.git#branch=main'Если меняешь схему так, что нарушаешь правила — buf breaking фейлит PR. Сэйф.
2.14 Tolerant reader в protobuf-сервисах Go
Заголовок раздела «2.14 Tolerant reader в protobuf-сервисах Go»import "google.golang.org/protobuf/proto"
// Получили message со «лишними» полями (от нового сервера)var u userpb.Userif err := proto.Unmarshal(data, &u); err != nil { /* */ }// Unknown fields сохранены в u.ProtoReflect().GetUnknown() и при serialize отправлены обратно.Unknown fields preservation — proto3 рантайм сохраняет неизвестные поля. При forwarding они не теряются.
2.15 Версионирование через client SDK
Заголовок раздела «2.15 Версионирование через client SDK»Stripe, AWS, GCP делают:
- API не имеет version в URL.
- Клиентский SDK имеет версию.
- При вызове SDK отправляет header
Stripe-Version. - Сервер по этому header выбирает behavior.
// Stripe Go SDKstripe.APIVersion = "2024-08-15"charge, _ := charge.Get("ch_123", nil)// Внутри SDK добавляется Stripe-Version: 2024-08-152.16 Date-based versioning
Заголовок раздела «2.16 Date-based versioning»Stripe approach: версия = дата (2024-08-15).
Преимущества:
- Fine-grained.
- Хронология понятна.
- Можно постепенно эволюционировать.
Сложности:
- Нужно поддерживать много версий поведения на сервере.
- Версии — feature-flag в коде.
2.17 Migration strategies
Заголовок раздела «2.17 Migration strategies»1. Big bang — все клиенты переходят разом (мало где работает).
2. Parallel running — v1 и v2 живут вместе, постепенная миграция.
3. Strangler — новые фичи только в v2, старые потом мигрируют.
4. Adapter layer — внутренний сервер на v2, gateway трансформирует v1 запросы.
2.18 Deprecation policy
Заголовок раздела «2.18 Deprecation policy»1. Анонс — за N месяцев.2. Mark deprecated в docs/headers (Deprecation: <date>).3. Sunset header (RFC 8594): Sunset: Tue, 31 Dec 2026 23:59:59 GMT.4. Логирование вызовов старых endpoints (metrics).5. Outreach: email клиентов, остающихся на v1.6. Soft deprecation: returns 200 + warning header.7. Hard removal — после deadline.2.19 Sunset header (HTTP)
Заголовок раздела «2.19 Sunset header (HTTP)»HTTP/1.1 200 OKSunset: Tue, 31 Dec 2026 23:59:59 GMTDeprecation: Wed, 01 Jan 2026 00:00:00 GMTLink: <https://api.example.com/v2/users/123>; rel="successor-version"2.20 Документация API версий
Заголовок раздела «2.20 Документация API версий»OpenAPI:
openapi: 3.0.0info: version: 1.0.0servers: - url: https://api.example.com/v1paths: /users/{id}: get: deprecated: true ...Buf BSR — генерирует docs из proto автоматически.
3. Gotchas
Заголовок раздела «3. Gotchas»3.1 ⚠️ Удалили поле «никто не использовал»
Заголовок раздела «3.1 ⚠️ Удалили поле «никто не использовал»»Решили дропнуть поле, посмотрели логи — нули. Через неделю — поломались клиенты, у которых cache holdover. Метрики != правда.
3.2 ⚠️ Required → Optional
Заголовок раздела «3.2 ⚠️ Required → Optional»Менять required на optional — backward compatible.
Менять optional на required — breaking! Старые клиенты не отправят поле.
3.3 ⚠️ Enum новый value
Заголовок раздела «3.3 ⚠️ Enum новый value»enum Status { ACTIVE = 0; INACTIVE = 1; BANNED = 2; // new value}Старые клиенты увидят UNKNOWN или fallback. Если они делают switch без default → panic. Всегда default-case в switch’е enum’ов.
3.4 ⚠️ Reuse field number
Заголовок раздела «3.4 ⚠️ Reuse field number»Удалили int32 age = 3 → потом переиспользовали string email = 3. Wire format ломается, старые данные читаются неверно. Всегда reserved.
3.5 ⚠️ JSON: null vs missing
Заголовок раздела «3.5 ⚠️ JSON: null vs missing»{"name": null} // явно null{} // отсутствуетСемантически разное, но в Go *string == nil для обоих. Может ломать update endpoints (PATCH).
3.6 ⚠️ Schema Registry compatibility mode = NONE
Заголовок раздела «3.6 ⚠️ Schema Registry compatibility mode = NONE»Кто-то выставил NONE — теперь любые изменения. Через месяц — chaos. Default — BACKWARD.
3.7 ⚠️ Mobile clients никогда не обновляются
Заголовок раздела «3.7 ⚠️ Mobile clients никогда не обновляются»10% пользователей на v1 даже через 2 года. Не получится «дропнуть v1 после 6 месяцев».
3.8 ⚠️ Внутренние клиенты «всегда обновим»
Заголовок раздела «3.8 ⚠️ Внутренние клиенты «всегда обновим»»Иллюзия. Если у тебя 30 микросервисов, обновить все за квартал — нереально. Even внутри компании нужно version-stability.
3.9 ⚠️ Семантика поля изменилась
Заголовок раздела «3.9 ⚠️ Семантика поля изменилась»Тип тот же (string), но раньше было “ISO 8601 datetime”, теперь “RFC 3339”. Технически не breaking, фактически — поломка. Семантика == часть API.
3.10 ⚠️ Buf breaking ругается на «реструктуризацию»
Заголовок раздела «3.10 ⚠️ Buf breaking ругается на «реструктуризацию»»Поменял Order { Item items } на Order { Items inner } — formal breaking, хотя wire format может быть совместим. Иногда правила слишком строгие.
3.11 ⚠️ Версии в URL + content negotiation одновременно
Заголовок раздела «3.11 ⚠️ Версии в URL + content negotiation одновременно»Не путай стратегии. Выбери одну и придерживайся.
3.12 ⚠️ Кеши и версии
Заголовок раздела «3.12 ⚠️ Кеши и версии»CDN кеширует /api/users/123 без учёта Accept-Version. Если кеш не учитывает header → cross-version mix. Используй Vary: Accept-Version.
3.13 ⚠️ “Internal API” — миф
Заголовок раздела «3.13 ⚠️ “Internal API” — миф»«Внутренние» API становятся public через интеграции, скрипты, ML pipelines. Версионируй и их.
3.14 ⚠️ GraphQL: меняешь required arg → breaking
Заголовок раздела «3.14 ⚠️ GraphQL: меняешь required arg → breaking»В GraphQL non-null arguments не добавляются. Сначала добавь nullable, потом мигрируй, потом — non-null.
4. Real cases
Заголовок раздела «4. Real cases»4.1 Stripe API versioning
Заголовок раздела «4.1 Stripe API versioning»- Date-based versions:
2024-08-15. - Header:
Stripe-Version. - Каждая версия — описанные изменения в changelog.
- Сервер поддерживает все версии (внутри feature-flag).
- 10+ лет старых версий ещё работают.
4.2 GitHub REST API
Заголовок раздела «4.2 GitHub REST API»- URL path
/v3/. - GraphQL для новых интеграций — preview features через header.
- Deprecation через Sunset header.
4.3 AWS — major version per service
Заголовок раздела «4.3 AWS — major version per service»aws-sdk-govsaws-sdk-go-v2(полностью новое API).- Внутри version — backward compatible.
4.4 Kubernetes API versioning
Заголовок раздела «4.4 Kubernetes API versioning»/api/v1 # core/apis/apps/v1 # extensions/apis/networking.k8s.io/v1beta1 # beta/apis/networking.k8s.io/v1 # stablev1alpha1→v1beta1→v1lifecycle.- Конверсия между версиями через webhook.
4.5 Protobuf в Google
Заголовок раздела «4.5 Protobuf в Google»- Internal: огромная база
.protoфайлов. - Глобальная политика: no breaking changes.
- Tooling: внутренний аналог Buf.
4.6 gRPC миграция в Авито/Озоне
Заголовок раздела «4.6 gRPC миграция в Авито/Озоне»- Часто: REST → gRPC внутри.
- Strangler pattern: API gateway транслирует HTTP/JSON → gRPC.
- Постепенно: внутренние сервисы переходят на gRPC native.
4.7 Schema Registry в Kafka pipelines
Заголовок раздела «4.7 Schema Registry в Kafka pipelines»- Каждый topic привязан к subject.
- Backward compatibility check before publish.
- При nondeterministic schema → отказ.
- Метрики: schema usage per consumer.
5. Вопросы (20+)
Заголовок раздела «5. Вопросы (20+)»- Какие 5 стратегий версионирования API ты знаешь? Pros/cons каждой.
- Что такое backward vs forward compatibility?
- Что такое breaking change? Перечисли 5 примеров.
- Что такое tolerant reader pattern?
- Какие правила эволюции protobuf?
- Зачем
reservedв protobuf? - Какие типы protobuf совместимы (int32 ↔ … )?
- Какие правила эволюции Avro?
- Что такое Schema Registry? Зачем?
- Чем
BACKWARDотличается отFULLв Schema Registry? - Какие subject naming strategies в Schema Registry?
- Как версионировать gRPC API?
- Как делается deprecation в GraphQL?
- Что такое
Sunsetheader? - Как использует версии Stripe?
- Зачем нужен Buf, что он делает?
- Что делает
buf breaking? - Как обрабатывать новые enum values в клиенте?
- Почему
DisallowUnknownFieldsв JSON — обычно плохо? - Какие проблемы при reuse field number в protobuf?
- Что делать, если 10% mobile клиентов на v1 через 2 года?
- Как версионировать events в Kafka?
- Чем
BACKWARDотличается отBACKWARD_TRANSITIVE? - Как deprecation работает в OpenAPI?
- Как организовать миграцию клиентов с v1 на v2?
6. Practice
Заголовок раздела «6. Practice»Задача 1: эволюция Protobuf без breaking
Заголовок раздела «Задача 1: эволюция Protobuf без breaking»- Создай proto
Order { string id; double total; }. - Добавь поле
currency— должно работать со старыми клиентами. - Удали поле через
reserved— добавь test «старый клиент читает новые данные». - Прогони
buf breakingпротив main — должно пройти.
Задача 2: Schema Registry + Avro
Заголовок раздела «Задача 2: Schema Registry + Avro»- Подними Confluent Platform (Schema Registry + Kafka) в Docker.
- Зарегистрируй Avro schema для topic
orders. - На Go: продюс события с этой схемой.
- Изменить схему backward-compatible (default value).
- Попытаться сделать breaking change → должен fail.
Задача 3: API v1 → v2 на Go
Заголовок раздела «Задача 3: API v1 → v2 на Go»- Реализуй HTTP API
/v1/users/{id}с полемname. - Добавь
/v2/users/{id}с полямиfirstName,lastName. - На уровне service layer — общая логика.
- На handler — разная сериализация.
- Добавь deprecation header в
/v1/.
Задача 4: Buf workflow
Заголовок раздела «Задача 4: Buf workflow»- Создай
buf.yaml,buf.gen.yaml. - Опиши proto файл.
buf generate→ получи Go stubs.- Сделай breaking change →
buf breakingфейлит. - Настрой в GitHub Actions:
buf breakingпротив main.
Задача 5: версионирование с date-based
Заголовок раздела «Задача 5: версионирование с date-based»- Сделай HTTP middleware, который читает
X-API-Version: 2026-01-15. - На основе версии — выбирает behavior (feature flags).
- Реализуй 2 версии endpoint (поле было
email, сталоcontact). - Документ в README: список версий и changes.
7. Источники
Заголовок раздела «7. Источники»- Roy Fielding — Architectural Styles and the Design of Network-based Software Architectures (REST dissertation)
- RESTful Web APIs — Leonard Richardson, Sam Ruby, Mike Amundsen
- Stripe Engineering — APIs as infrastructure: future-proofing Stripe with versioning: https://stripe.com/blog/api-versioning
- Designing Web APIs — Brenda Jin et al. (O’Reilly)
- Google AIP (API Improvement Proposals): https://google.aip.dev/
- Microsoft REST API Guidelines: https://github.com/microsoft/api-guidelines
- Protocol Buffers — Language Guide: https://protobuf.dev/programming-guides/
- Buf documentation: https://buf.build/docs/
- Confluent Schema Registry: https://docs.confluent.io/platform/current/schema-registry/
- Karapace: https://github.com/Aiven-Open/karapace
- Apicurio Registry: https://www.apicur.io/registry/
- GraphQL deprecation: https://graphql.org/learn/best-practices/#versioning
- RFC 8594 — Sunset HTTP Header: https://datatracker.ietf.org/doc/html/rfc8594
- Documenting REST APIs with OpenAPI — Swagger/OpenAPI specs
- Apollo GraphQL versioning: https://www.apollographql.com/docs/technotes/TN0021-graph-versioning/
- Buf Schema Registry: https://buf.build/docs/bsr/
- AsyncAPI for event-driven APIs: https://www.asyncapi.com/
Резюме. API versioning — это не «выбрать /v1 или header», это методика эволюции схем без поломки клиентов. Tech lead понимает: protobuf rules (reserved, type compatibility), Schema Registry compatibility modes, Buf для CI-проверок, GraphQL deprecation lifecycle. URL path — самая популярная стратегия для REST. Date-based versioning (Stripe) — для тонкой эволюции. Главное правило: никогда не ломай прод-клиентов, всегда добавляй новое и deprecate старое с явным sunset.