Попробовать API
</>
Версии SDK и semver: как выпускать обновления, не ломая партнёрские интеграции
platformaИнструкция
13 мин

Версии 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):

  • Удаление или переименование метода, поля, параметра.
  • Новое обязательное поле в запросе.
  • Смена типа поля (stringobject) или HTTP-статуса (200202).
  • Удаление значения 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 (12)есть breaking changeчитай migration guide

Партнёр ставит пакет с caret-диапазоном — и этим сам выбирает, что ему доезжает автоматически:

bash
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 driftcreated_at типизировали в PHP как string, в Python как datetime, в Node как Date | string, и смена формата на сервере превращается в три раскоординированных PR. Coverage drift — сервер отдаёт 21 эндпоинт, PHP покрывает все, Python — часть, Node отстаёт на квартал. Держать паритет ручным трудом можно только при отдельном человеке, чья единственная задача — следить за паритетом.

В BOTIX источник правды один — файл openapi.yaml (OpenAPI 3.0) в корне репозитория платформы. Он описывает каждый эндпоинт, модель, код ошибки, поле запроса и ответа. Из него генерируется всё остальное. Цикл выпуска:

  1. Правка в openapi.yaml — новый эндпоинт, поле или код ошибки.
  2. openapi-generator-cli с пресетом PHP → обновлённый sdk-php.
  3. То же для Python → sdk-python.
  4. То же для Node → sdk-node.
  5. Из той же спеки рендерится Redoc — публичная документация на developers.botix.pro.
  6. Из той же спеки рендерится Swagger UI на developers.botix.pro/try-it — интерактивная песочница.

Один файл — шесть артефактов, любое изменение распространяется через генератор за минуту, без копипасты. Именно поэтому структура методов совпадает по языкам: client.contacts.contactsList(...) выглядит одинаково в PHP, Python и Node, и разработчик при смене стека не переучивается.

Отдельная ценность для версионирования — примеры кода не расходятся с API, потому что живут внутри спеки. Расширение x-codeSamples держит сниппеты прямо под эндпоинтом:

yaml
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):

bash
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-проверка обновления занимает час, не недели.