Попробовать API
</>
SDK BOTIX 1.1: zero-deps, генерация из OpenAPI и триал
sdkРазбор
15 мин

SDK BOTIX 1.1: zero-deps, генерация из OpenAPI и триал

Вышло обновление публичных SDK BOTIX — версия 1.1 для PHP, Python и Node. TL;DR: три архитектурных изменения — ноль транзитивных зависимостей, генерация клиентов из единого `openapi.yaml` и триал-режим вместо отдельного sandbox-сервера. Ниже разбираю каждое по сути: зачем так сделано, что это даёт интегратору и как мигрировать с 1.0 без breaking changes.

Коротко

  • Zero-deps. Все три SDK переведены на stdlib своих языков (ext-curl / urllib.request / встроенный https). Секция dependencies пустая — composer audit / npm audit / safety check зелёные по дизайну, а не по везению.
  • Генерация из openapi.yaml. Единственный источник правды — спека в репозитории платформы. Из неё генерируются PHP/Python/Node SDK, Redoc-документация, Swagger UI и Postman/Bruno-коллекции. Никакой рассинхронизации трёх ручных клиентов.
  • Триал вместо sandbox. Отдельного тестового контура нет. Разработчик тестирует тот же production-контур, изолированный на уровне проекта и ограниченный лимитами тарифа. Класс ошибок «в sandbox работало, в проде нет» убран архитектурно.

Установка

Команды установки прежние, версия — 1.1.0:

bash
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0

Исходники открыты в организации github.com/BOTIX-pro — репозитории sdk-php, sdk-python, sdk-node. Лицензия открытая, PR принимаются. Эндпоинты не изменились — базовый префикс по-прежнему https://api.botix.pro/public/v1/, авторизация — Authorization: Bearer XXXXXXXX… (ключ выдаётся в кабинете и показывается один раз).

Тезис 1. Ноль транзитивных зависимостей — и почему это про риск, а не про эстетику

SDK для публичного API — это не приложение, а тонкая обёртка над HTTP. Каждая её зависимость становится головной болью того, кто её установит. Заходишь на страницу документации, видишь pip install/composer require/npm install, ставишь — и через минуту в requirements.txt или composer.lock появляется не одна новая строка, а четырнадцать: SDK притащил HTTP-клиент, JSON-валидатор, UUID-библиотеку, свой retry-механизм, ещё один логгер и пару утилитарных пакетов от одного мейнтейнера «в свободное время». Каждая из них создаёт ровно три класса проблем.

Конфликты версий. SDK тянет requests==2.28.0, у интегратора в проекте уже стоит requests==2.31.0 из-за собственного фреймворка. При жёстком pin установка падает конфликтом; при мягком (>=2.20,<3.0) — встанет одна из совместимых версий, и обновление фреймворка через полгода может сломать совместимость с SDK. В Python это особенно больно: глобальное пространство имён не позволяет держать две версии requests одновременно. В Node чуть лучше за счёт node_modules per-package, но дубли раздувают бандл.

Supply-chain атаки. Каждая транзитивная зависимость — вектор атаки. История полна примеров: event-stream (мейнтейнер отдал доступ незнакомцу, тот добавил кражу криптокошельков), ua-parser-js (взлом аккаунта, релиз с майнером), colors.js («протест-релиз», ломающий всех зависимых). Когда SDK тянет 14 прямых зависимостей, общее дерево транзитивных пакетов легко переваливает за 100, и npm audit / safety check начинают регулярно ругаться на advisories в зависимостях зависимостей — которые интегратор пофиксить не может, потому что не контролирует SDK.

Устаревание. Зависимости стареют. Свежая год назад сегодня имеет известную уязвимость. Активно поддерживаемый SDK обновляет их вовремя; заброшенный — превращает каждую зависимость в мину под интегратором.

Альтернатива — zero-dependency SDK: только стандартная библиотека языка, ничего больше. В BOTIX так построены все три клиента:

SDKЧто используетсяЧто убрано
PHPext-curl из стандартной поставкиguzzlehttp/guzzle, psr/log
Pythonurllib.request из stdlibrequests, urllib3
Nodeвстроенные модули https, httpaxios, node-fetch

composer.json PHP-SDK не имеет ни одной зависимости в секции require кроме php и ext-curl. У Python-SDK пустой requirements.txt. У Node-SDK нулевая секция dependencies (только devDependencies под билд, в продакшен не попадают).

Что это даёт интегратору. composer audit, npm audit, safety check показывают зелёный статус по дизайну. Размер бандла Node-приложения после установки @botix/sdk увеличивается на 30-40 КБ, а не на 2 МБ. Время старта не страдает от dynamic-import тонн транзитивных модулей.

Возражение «requests удобнее urllib, зачем городить с stdlib» справедливо для прикладного кода. Но SDK — не прикладной код: он живёт у интегратора в продакшене, его задача — минимизировать риск, а не быть красивым внутри. HTTP-клиент из stdlib покрывает 95% сценариев (GET/POST/PUT/DELETE, заголовки, тело, чтение ответа); HTTP/2-мультиплексирование и connection pooling публичному API через интернет не нужны — HTTP/1.1 работает без потерь. Правило перед установкой любого чужого SDK простое: открой его package.json / composer.json / setup.py на GitHub и посмотри секцию dependencies. 0-2 пакета — уважение к интегратору, 5-7 — приемлемо, 10+ — груз чужой экосистемы. Подробнее про сам принцип и открытый код — в разборе Zero-deps SDK: генерация из OpenAPI и open-source.

Структура SDK одинаковая во всех трёх языках — метод client.contacts.create(...) есть везде, аргументы те же, ответ той же структуры:

python
from botix import Client
client = Client(api_key=XXXXXXXX["BOTIX_API_KEY"])
contact = client.contacts.create(phone="+79991234567", name="Test")
javascript
const { Client } = require('@botix/sdk');
const client = new Client({ apiKey: XXXXXXXX });
const contact = await client.contacts.create({ phone: '+79991234567', name: 'Test' });

При переключении между языками переучиваться не нужно. Как эта идентичность вообще достигается — второй тезис.

Тезис 2. Генерация из openapi.yaml вместо трёх ручных SDK

Версия 1.0 писалась руками: PHP отдельной командой, Python отдельной, Node отдельной. К концу первого квартала эксплуатации стало понятно, что три параллельные команды держат паритет с трудом — на каждое изменение API уходило три PR с разной скоростью. Ручной SDK ломается тремя способами, и все три неизбежны.

Documentation drift. API получает новый endpoint, его добавляют в код сервера и в документацию. Через две недели дописывают в PHP-SDK, через месяц — в Python, через два — в Node. В этом промежутке клиенты на разных языках видят разный набор возможностей, а на странице документации метод уже есть — и прилетает тикет «у вас в доках метод X, а в SDK его нет».

Type drift. Сервер отдаёт created_at строкой ISO 8601. В PHP его типизировали как string, в Python — как datetime, в Node — как Date | string. Меняется формат на Unix timestamp — на сервере один коммит, в трёх SDK три PR, три релиза, три обновления доков, и синхронно они не пройдут почти никогда.

Coverage drift. Сервер имеет 60 endpoints. PHP-SDK покрывает 60, Python — 55, Node — 47. Держать паритет ручным трудом реально только с отдельным человеком, у которого это единственная задача.

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

  • SDK на PHP через openapi-generator-cli;
  • SDK на Python через тот же инструмент;
  • SDK на Node через тот же инструмент;
  • публичная документация Redoc на developers.botix.pro;
  • Swagger UI на developers.botix.pro/try-it для пробных запросов из браузера;
  • Postman-коллекция и Bruno-коллекция для CLI-тестирования.

Один файл — набор артефактов. Любое изменение API распространяется через генератор за минуту, без копипасты и рассинхронизации.

Отдельная история — код-сэмплы в документации. Обычно они лежат в Markdown и синхронизируются с API вручную (то есть никогда до конца). В OpenAPI есть расширение x-codeSamples — примеры хранятся прямо в спеке, под каждым endpoint:

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',
            ]);

На момент выхода 1.1.0 таких блоков внутри openapi.yaml — 92, по три-четыре языка (curl, PHP, Python, Node) на каждый осмысленный endpoint. Меняется сигнатура метода — сэмпл правится в одном месте и через рендер Redoc сам попадает на страницу документации.

Что держать в голове при генерации. Сгенерированный SDK — не «бесплатное решение под ключ»:

  • Качество спеки определяет качество SDK. oneOf без discriminator не даст корректный union-тип в TypeScript; код ошибки, указанный в description, но не в responses, клиент не научится обрабатывать. Любая неточность всплывёт во всех SDK одновременно.
  • Шаблоны генератора — точка кастомизации. Нет strict types в стандартном PHP-шаблоне или timing-safe сравнения в verifyWebhook — правится один раз в шаблоне, а не руками в сгенерированном коде; при следующей регенерации правки сохранятся.
  • Ручные надстройки живут отдельно. Нестандартные паттерны (long-polling с особой логикой реконнекта, batch-склейка запросов, Laravel facade / Django app) кладутся поверх генерации: сгенерированное — в src/Generated/ (перезаписывается целиком), ручное — в src/Extension/ (остаётся нетронутым). Конфликт становится явным при сборке.
  • Тесты пишутся отдельно. Генератор покрывает структуру — типы, сигнатуры, сериализацию. Поведенческие тесты (что метод шлёт правильный запрос и верно читает ответ) пишутся руками, но их объём в разы меньше объёма ручного SDK.

Итог тезиса: любой ручной SDK — технический долг с первого дня, растущий пропорционально числу языков, endpoints и скорости изменений API. Спека — не «работа сверху», а центральный артефакт, из которого вытекает остальное.

Тезис 3. Триал вместо отдельного sandbox

Версия 1.0 предполагала, что для тестирования нужен отдельный sandbox: отдельный домен, отдельная база, отдельные ключи, отдельная панель. Логика понятная — дать разработчику место, где можно ломать всё, не задевая боевые данные. Проблема в том, что любой второй контур — это не половина прода, а отдельный продукт со своей жизнью, которая со временем расходится с продом:

  • Дублирование инфраструктуры — своим серверам БД, очередям, кешу и воркерам sandbox стоит денег; экономят — тайминги другие, тесты ненадёжны.
  • Дублирование миграций и фич — каждую надо применить дважды, команда забывает, через полгода в sandbox устаревшие поля и старая логика ошибок, заводится тикет «синхронизировать sandbox с prod».
  • Расхождение поведения — самый болезненный пункт: sandbox считают «безопасным», ослабляют rate-limit, выключают fraud-фильтры, смягчают валидацию. Интеграция работает в sandbox, переключается на prod — падает на первом запросе. Это не лень разработчика, а архитектурное расхождение, заложенное платформой.

В 1.1 отдельного sandbox-окружения нет вовсе. Вместо него — триальный кабинет, который физически живёт на том же production-контуре, но имеет жёсткие лимиты и флаги, ограничивающие побочные эффекты. На практике это значит:

  • триальный проект пишет в ту же базу, что и платный;
  • запросы идут через те же FPM-воркеры, те же FastAPI-сервисы, ту же Redis-очередь;
  • те же миграции, та же логика обработки ошибок, те же коды ошибок, те же заголовки в ответе.

Различия — только в трёх местах:

  1. Лимиты тарифа. Поле api_rate_per_minute для триала ниже (30/мин против 2000/мин на тарифе «Бизнес»). Это тот же механизм rate-limit, что и у платных клиентов, просто с более жёстким порогом — разработчик на триале сталкивается ровно с тем rate-limit, что увидят его пользователи на проде.
  2. Toggle on-write операций. В кабинете доступен api_write_enabled = false — read-only режим: все POST/PUT/DELETE возвращают 403 TRIAL_READ_ONLY. Удобно для смоук-тестов чтения без риска что-то изменить. Тот же тогл есть и у платных клиентов.
  3. Срок жизни. Триал — 14 дней без карты. После этого кабинет переходит в архивное состояние (read-only по умолчанию, до оплаты). Не успел протестировать — заводишь второй триал или оплачиваешь минимальный тариф.

Регистрация developer-триала — на app.botix.pro/developers/quickstart. После регистрации сразу выдаётся API-ключ, без мастера настройки (мастер нужен владельцу бизнеса, а не разработчику).

bash
# Триал — те же эндпоинты, тот же формат
curl -X POST https://api.botix.pro/public/v1/contacts \
  -H "Authorization: Bearer XXXXXXXX…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+79991234567","name":"Test"}'

Почему это работает: разработчик тестирует ту же систему, которую увидит на проде, — не её модель, не урезанную копию, не сборку трёхмесячной давности. Это убирает целый класс ошибок: «поле было в sandbox, в проде нет» — невозможно (контур один); «в sandbox 200, в проде 422» — невозможно (валидаторы те же); «webhook в sandbox шлётся, в проде нет» — невозможно (обработчик тот же); «заголовок X-RateLimit в sandbox другого формата» — невозможно (middleware один). Бонусом команда платформы тратит ноль усилий на синхронизацию контуров — синхронизировать нечего.

Граница «триал/прод» проходит не в инфраструктуре, а в данных и правах: триальный проект — это запись в btx_projects с флагом is_developer_trial = true и тарифом «триал» со своими limits. API-ключ триала видит только данные своего проекта — так же, как любой другой ключ, через ту же Auth::assertProjectAccess(). Разработчик может делать с API что угодно (создавать контакты, слать webhook, перегружать запросами), но воздействует только на свой проект.

Сравнение по затратам команды:

Затратаdev/staging/prodТриал-как-sandbox
Серверых2-х3х1
МиграцииКаждую применить дваждыОдин раз
Тестовые данныеПоддерживать отдельноНе нужны
Документация«Различия между sandbox и prod»Не нужна
Тикеты «в sandbox работает, в prod нет»РегулярноНе возникают

Где подход неприменим — честно, случаи есть. Если каждый POST реально двигает деньги (/payments/charge), нужен тестовый ключ эквайринга или test-mode на уровне ключа. Если API шлёт SMS/push/email — рассылка на боевые номера недопустима, нужен «test recipient» или режим «логировать, но не доставлять». Если API управляет физическим оборудованием — тестировать на проде нельзя в принципе, тут dev/staging/prod единственный путь. В BOTIX эти случаи закрываются флагами в самих модулях: у bot-preview есть $context['preview']=true — модуль пишет в лог, но не отправляет реальное сообщение в Telegram. Это снимает риск побочных эффектов без второго контура. Пошаговый онбординг на триале разбираем отдельно — первый день с API за 7 шагов и квик­старт с триал-sandbox и sub-проектами.

Что ещё приехало в 1.1

Помимо трёх главных изменений, в релиз попали два helper'а, которые раньше каждый интегратор писал руками.

Webhook helper с verify + timestamp. В 1.0 проверка подписи сводилась к ручному HMAC, где повторялись две ошибки: сравнение через == вместо compare_digest (уязвимо к timing-атакам) и игнорирование X-BOTIX-Timestamp (открыта дверь replay-атакам). В 1.1 во всех трёх SDK есть готовый helper:

python
from botix import Webhook

if not Webhook.verify(secret, raw_body, signature_header, timestamp_header):
    return Response(status=401)

Внутри — сравнение в постоянном времени, проверка timestamp на свежесть в окне ±5 минут, единый формат заголовка. Аналогично в PHP (Botix\SDK\Webhook::verify) и Node (Webhook.verify).

Автоматический retry с экспоненциальным backoff. В 1.0 при 429 Too Many Requests SDK возвращал ошибку, и retry-логика была на стороне интегратора. В 1.1 ретраи встроены: по умолчанию 5 попыток с экспоненциальной задержкой и jitter, читает Retry-After из ответа платформы, не повторяет 4xx кроме 429:

python
from botix import Client, RetryConfig

client = Client(api_key=..., retry=RetryConfig(max_attempts=5, jitter=True))
# отключить retry: RetryConfig(max_attempts=1)

Оба слоя разбираем глубже в связке про безопасный обработчик и продакшен-надёжность — SDK без зависимостей и webhook-симулятор.

Как мигрировать с 1.0

Бинарной несовместимости нет — все методы 1.0 работают в 1.1 без изменений. Что нужно сделать:

  1. Обновить пакет: composer update botix-pro/sdk, pip install --upgrade botix, npm update @botix/sdk.
  2. Если код проверяет webhook-подпись вручную — переключиться на Webhook::verify() из SDK. Старый код продолжит работать, но в нём с большой вероятностью есть timing-attack-уязвимость.
  3. Если код пишет собственный retry-цикл — отключить его или оставить как есть (SDK не дублирует retry поверх существующего).
  4. Если используется sandbox-домен (был только в pre-release 1.0) — переключиться на основной api.botix.pro/public/v1/* с триал-ключом.

Полная инструкция миграции — в каждом SDK-репозитории файл MIGRATION.md. Breaking changes на 1.0 → 1.1 нет. Про semver самих SDK и обновления без сюрпризов — версии SDK и semver.

Что осталось как было

Эндпоинты не менялись — api.botix.pro/public/v1/*. Идемпотентность по-прежнему обязательна на мутирующих эндпоинтах через заголовок Idempotency-Key. Audit-лог в кабинете на app.botix.pro/developers показывает все запросы со SDK 1.0 и 1.1 одинаково.

Версионирование SDK не совпадает с версионированием API — это сознательно. API сейчас в 1.1.0, SDK тоже 1.1.0, но дальше они могут расходиться: например, SDK 1.1.5 может работать с API 1.1.0 (патчи только SDK, без изменений контракта). Связь поддерживается через MIGRATION.md в каждом репозитории.