Версии SDK и semver: как выпускать обновления, не ломая партнёрские интеграции
TL;DR: партнёр встроил ваш SDK и ушёл. Через месяц вы выпускаете обновление — и оно либо доезжает незаметно, либо роняет его прод. Разница держится на трёх вещах: дисциплине «что ломает контракт, а что нет», на том, что три SDK генерируются из одного `openapi.yaml`, и на том, что код открыт — security-команда партнёра читает дифф спеки, а не гадает. Ниже — как это устроено в BOTIX и как повторить у себя.
Коротко
- Breaking или нет — решает не размер правки, а сломается ли вчерашний код партнёра. Новое опциональное поле — минор; удаление или смена типа — мажор. Партнёру доезжает только то, что он попросил caret-диапазоном
^1.1; о мажоре его предупреждает сам API заголовкомX-API-Deprecation. - Три SDK не пишутся руками — они генерируются из
openapi.yaml. Один источник правды → PHP, Python, Node с одинаковой структурой методов + Redoc-документация + Swagger-песочница. Breaking виден в диффе спеки раньше, чем в трёх кодовых базах. - Код SDK открыт на
github.com/BOTIX-pro. Это не подарок сообществу, а способ убрать блокировку security-команды партнёра: она читает репозиторий и диффopenapi.yamlза час, а не запускает NDA-процедуру на недели.
Это инструкция по выпуску версий клиентских SDK так, чтобы обновление не превращалось в инцидент у интегратора. Всё на устройстве SDK BOTIX: три библиотеки (PHP botix-pro/sdk, Python botix, Node @botix/sdk), текущая версия 1.1.0, все сгенерированы из единого openapi.yaml. Public API живёт на https://api.botix.pro/public/v1/, документация — на developers.botix.pro. Разберём три опоры по очереди: контракт совместимости, генерацию из спеки и открытый код.
Тезис 01. Что ломает контракт партнёра, а что нет
Когда команда подключается к публичному API, она неявно подписывается на условия: эти эндпоинты возвращают такие-то поля, ошибки выглядят так-то, коллекция пагинируется вот так. Breaking change — это любое изменение, из-за которого существующий код интегратора сломается или начнёт работать иначе без его явного действия. Не размер правки решает, а этот критерий.
Держите этот список перед глазами при каждом релизе:
Безопасно (MINOR или PATCH):
- Новый эндпоинт или новый метод в SDK.
- Новое опциональное поле в запросе.
- Новое поле в ответе — при условии, что клиент игнорирует неизвестные поля (это требование контракта, а не предположение).
- Новое значение enum, если старые остаются и клиент не валидирует множество как закрытое.
- Внутренний рефакторинг без изменения сигнатур.
Ломает (только MAJOR):
- Удаление или переименование метода, поля, параметра.
- Новое обязательное поле в запросе.
- Смена типа поля (
string→object) или HTTP-статуса (200→202). - Удаление значения enum.
- Изменение поведения по умолчанию.
Две ловушки, которые чаще всего проносят breaking под видом минора. Первая — enum: если ваш openapi.yaml описывает поле как закрытое множество, а партнёр сгенерировал SDK со строгой валидацией, добавление нового значения сломает ему парсинг. Либо документируйте enum как открытый, либо считайте расширение мажорным. Вторая — семантика: если POST /public/v1/contacts раньше обрабатывал дубликаты как ошибку, а теперь тихо их пропускает — это breaking, даже когда формат запроса и ответа не изменился ни на байт.
Semver — это обещание, которое интегратор читает по номеру версии:
| Часть | Когда растёт | Обещание интегратору |
|---|---|---|
| PATCH (1.1.0 → 1.1.1) | багфикс, ничего в контракте не поменялось | обновляйся вслепую |
| MINOR (1.1 → 1.2) | новые методы/поля, старое работает | обновляйся вслепую |
| MAJOR (1 → 2) | есть breaking change | читай migration guide |
Партнёр ставит пакет с caret-диапазоном — и этим сам выбирает, что ему доезжает автоматически:
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0^1.1 в Composer/npm подхватит 1.2, 1.3, но не 2.0. Это и есть механизм «обновления без breaking»: минор доезжает автоматически, мажор — только руками. Ваша задача при релизе — не соврать в номере.
Мажор нельзя просто выложить и надеяться, что партнёр заметит. У BOTIX депрекация старой версии живёт 12 месяцев с момента, когда новая мажорная объявлена стабильной; срок зафиксирован публично на developers.botix.pro/versioning, безопасностные фиксы при этом продолжают приезжать на обе версии. И — главное — старую версию нельзя оставлять немой: каждый ответ депрекейтнутого эндпоинта несёт машиночитаемый заголовок
X-API-Deprecation: version=1.0.0; sunset=2027-05-22; replacement=/public/v2Хороший SDK ловит его и пишет предупреждение в stderr; хорошая интеграционная команда мониторит лог и заводит задачу на миграцию задолго до sunset. Рядом живёт X-API-Notice — для информационных сигналов, не привязанных к депрекации («через час окно техобслуживания», «достигнуто 80% месячного лимита»). Полный список изменений между версиями — в developers.botix.pro/migration-guide с примерами «было/стало» на всех языках SDK, а changelog параллельно отдаётся через developers.botix.pro/changelog/rss: у команд, которые держат API в проде, обычно уже есть feed-агрегатор или Slack-бот на лентах.
Чего делать нельзя: прятать breaking в патч-релизах с формулировкой «улучшение надёжности», делать silent deprecation (эндпоинт молча меняет структуру) и смешивать URL-версионирование с header-версионированием. Выберите одну схему и фиксируйте её. Как именно устроен полный контракт ошибок и уведомлений на стороне платформы — в разборе white-label через API: что нужно, чтобы стать платформой для интеграторов.
Тезис 07. Почему три SDK генерируются из openapi.yaml, а не пишутся руками
Ручной SDK ломается тремя способами, и все три неизбежны. Documentation drift — метод уже в доках, но в PHP-SDK его напишут через две недели, в Python через месяц, в Node через два; клиенты на разных языках видят разный набор возможностей. Type drift — created_at типизировали в PHP как string, в Python как datetime, в Node как Date | string, и смена формата на сервере превращается в три раскоординированных PR. Coverage drift — сервер отдаёт 21 эндпоинт, PHP покрывает все, Python — часть, Node отстаёт на квартал. Держать паритет ручным трудом можно только при отдельном человеке, чья единственная задача — следить за паритетом.
В BOTIX источник правды один — файл openapi.yaml (OpenAPI 3.0) в корне репозитория платформы. Он описывает каждый эндпоинт, модель, код ошибки, поле запроса и ответа. Из него генерируется всё остальное. Цикл выпуска:
- Правка в
openapi.yaml— новый эндпоинт, поле или код ошибки. openapi-generator-cliс пресетом PHP → обновлённыйsdk-php.- То же для Python →
sdk-python. - То же для Node →
sdk-node. - Из той же спеки рендерится Redoc — публичная документация на
developers.botix.pro. - Из той же спеки рендерится Swagger UI на
developers.botix.pro/try-it— интерактивная песочница.
Один файл — шесть артефактов, любое изменение распространяется через генератор за минуту, без копипасты. Именно поэтому структура методов совпадает по языкам: client.contacts.contactsList(...) выглядит одинаково в PHP, Python и Node, и разработчик при смене стека не переучивается.
Отдельная ценность для версионирования — примеры кода не расходятся с API, потому что живут внутри спеки. Расширение x-codeSamples держит сниппеты прямо под эндпоинтом:
paths:
/contacts:
post:
x-codeSamples:
- lang: curl
source: |
curl -X POST https://api.botix.pro/public/v1/contacts \
-H "Authorization: Bearer $BOTIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone":"+79991234567","name":"Test"}'
- lang: PHP
source: |
$contact = $client->contacts()->create([
'phone' => '+79991234567',
'name' => 'Test',
]);Внутри openapi.yaml BOTIX таких блоков 92 — по три-четыре языка на каждый осмысленный эндпоинт. Меняется сигнатура метода — сниппет правится в одном месте и через рендер Redoc сам попадает на страницу документации. Токены в примерах — всегда плейсхолдеры (btx_live_<ваш-ключ>), не реальные ключи.
Что даёт это для совместимости, а не только для удобства:
- Контракт задаётся в одном месте. Добавили опциональное поле — регенерация разложила его во все три SDK одинаково. Никакого «в Python забыли».
- Breaking виден в диффе спеки. Удаление поля или смена типа заметны в PR к
openapi.yamlраньше, чем в трёх раздельных кодовых базах. Решение «это мажор» принимается по одному диффу. - Дисциплина «SDK без бизнес-логики». Сгенерированный SDK делает ровно три вещи: формирует запрос, подписывает, парсит ответ. Логики в нём нет — значит, и ломать в ручных правках нечего.
Генерация — не «бесплатное решение под ключ», и это надо понимать заранее. Качество спеки определяет качество SDK: oneOf без discriminator не даст корректный union в TypeScript, код ошибки в description вместо responses не будет обработан клиентом — любая неточность всплывёт во всех SDK сразу. Кастомизация идёт через шаблоны генератора, а не правкой сгенерированного кода: сгенерированное кладётся в src/Generated/ и перезаписывается целиком, ручные надстройки (Laravel facade, особый реконнект long-polling) — в src/Extension/, который при регенерации не трогается. И версия SDK ≠ версии API: API может быть 1.1.0, а SDK 1.1.2, где третья цифра — патчи самих шаблонов; связь держится через MIGRATION.md в каждом репозитории. Подробнее про генерацию и покрытие методов — в разборе zero-dependency SDK в open-source: стратегия платформы.
Тезис 20. Зачем открывать исходный код SDK
Открытый код SDK обсуждают через «щедрость»: принести пользу сообществу против «лучше потратим время на фичи». В таком виде спор упирается в мораль. На деле open-source SDK — инструмент с конкретными функциями для версионирования и доверия.
Функция первая — security-аудит без участия поставщика. Корпоративная интеграция почти всегда проходит через security-команду. Закрытая библиотека для неё — чёрный ящик: либо заблокировать (частый исход), либо требовать SOC 2 / ISO 27001 / DPA (десятки тысяч долларов и месяцы), либо просить исходники под NDA (недели). Открытый репозиторий закрывает все три: security открывает github.com/BOTIX-pro, видит, что SDK делает только заявленное, и пропускает интеграцию. Для обновлений это прямая экономия: дифф openapi.yaml между версиями читается за час, и проверка минорного апдейта не превращается в новый цикл согласования.
Функция вторая — bug fix не блокируется вендором. В закрытом SDK ошибка — это тикет в поддержку и новая версия через месяц-три; всё это время у интегратора костыль. В открытом — партнёр либо шлёт PR, либо временно форкает и патчит через VCS-ссылку в composer.json/package.json, пока upstream не примет фикс. Никакой блокировки на чужом релиз-цикле.
Функция третья — сигнал зрелости API. Публикация SDK под открытой лицензией читается как «у API есть стабильный контракт, который не стыдно показать». Закрытый SDK почти всегда прочитывается как «наколхозили, поэтому прячут» — не обязательно правда, но типовое восприятие. В BOTIX сигнал встроен в стратегию SDK с самого начала: три SDK (PHP, Python, Node), CLI и три example-репозитория для популярных фреймворков (Laravel, Express, Flask) выложены в организацию BOTIX-pro с открытой лицензией, а пакеты опубликованы в Composer (botix-pro/sdk), PyPI (botix) и npm (@botix/sdk):
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0Функция четвёртая — контрибьюции. На ранних этапах open-source SDK — односторонняя отдача, и это нормальный режим. По мере роста появляются PR с типовыми улучшениями: retry, timeout, прокси, совместимость с нетипичной версией зависимости — каждый такой PR — часы, которые поставщик не потратил. У зрелых SDK (Stripe, Twilio, Slack) — десятки внешних контрибьюторов; у молодых — единицы или ноль, и это тоже нормально. SDK BOTIX молодые, контрибьюций пока нет — честная оценка делается через 90 дней после релиза.
Страхи поставщика решаются дисциплиной, а не закрытостью. «Утащат код» — SDK бесполезен без API, к которому обращается; переписать под свой API легче, чем переиспользовать чужой. «Раскроем архитектуру» — не раскроем, если SDK не содержит бизнес-логики: он формирует запрос, подписывает, парсит ответ, а индексы категорий и расчёты живут на стороне API и так публикуются в openapi.yaml. «Поддержка issues» — реальная работа: без выделенного человека или ротации репозиторий превращается в кладбище с просрочкой по 200 дней, и это антиреклама; в BOTIX этот пункт в фазе становления.
Открывать не всегда нужно. Если API нишевой (два-три интегратора, все по NDA), если он в стадии активного breaking-change, если некому ревьюить PR — открытие ухудшит ситуацию. Все три условия — про ранние стадии; когда API стабилизируется, закрытый SDK превращается в якорь, мешающий росту. Зачем это владельцу бизнеса, а не только его разработчику, — в бизнес-разборе где хранятся данные: серверы, 152-ФЗ, доступ.
Мини-runbook релиза
Собранная последовательность, каждый шаг которой закрывает один класс инцидента:
1. Правка контракта -> openapi.yaml (PR, дифф на breaking)
2. Классификация -> patch / minor / major по списку из тезиса 01
3. Регенерация -> openapi-generator-cli -> 3 SDK
4. Версия -> один номер во все три пакета
5. CI-матрица зелёная -> PHP × Python × Node + example-apps
6. Синхронные теги -> v-x.y.z в sdk-php / sdk-python / sdk-node
7. Публикация -> одна команда -> Composer / PyPI / npm
8. Отзыв publish-ключей -> сразу после выкладкиШаг 2 закрывает «сломали партнёра тихо», шаг 3 — «SDK разошлись между языками», шаг 6 — «версии рассинхронились», шаг 8 — «утёк publish-токен». Про последнее отдельно: токен пакетного менеджера используется на релиз и отзывается сразу после — утёкший publish-токен позволяет выложить троянский релиз под вашим именем. В самих SDK и примерах любые credential-строки — плейсхолдеры (btx_live_XXXX…), не реальные ключи. Публикация — только с зелёной CI-матрицы, чтобы не выложить то, что не собирается на PHP 8.2. Три SDK живут в трёх репозиториях, но версия у них общая — теги ставятся синхронно, v1.2.0 выходит в sdk-php, sdk-python и sdk-node одной волной. Рассинхрон — худшее, что можно сделать с трёхъязычным SDK: партнёр на Python видит 1.2, партнёр на Node — всё ещё 1.1, и обещание «одинаковый контракт» ломается.
Что это даёт партнёру
- Он ставит
^1.1и получает минорные обновления автоматически, без страха, что прод упадёт. - Мажор он видит явно — по номеру и по заголовку
X-API-Deprecationв самих ответах, — и обновляется, когда готов, а не внезапно. - Один и тот же контракт в PHP, Python, Node: сменил стек — переучиваться не надо.
- Исходники открыты на
github.com/BOTIX-pro, диффopenapi.yamlмежду версиями читается за минуты — security-проверка обновления занимает час, не недели.
Попробуйте API BOTIX
Получите ключ, отправьте первый запрос и поймайте webhook — прямо в браузере, без боевого канала.
Попробовать API бесплатноЧитайте также
Zero-dependency SDK, open-source и партнёрский API
API как платформа: multi-tenant, white-label и open SDK
Как публичный API становится платформой для интеграторов
Генерация SDK из OpenAPI, white-label и open-source
