Переписать API — задача нервная. Особенно если на нём висят мобильные клиенты, сторонние интеграции и пара-тройка микросервисов, которые никто толком не документировал. Один неловкий деплой — и у вас 500-е ошибки, злые пользователи и ночной инцидент.
При этом «просто не трогать» тоже не работает: старые эндпоинты копят технический долг, тормозят разработку и в какой-то момент становятся узким местом всей системы.
Вот как делать миграцию так, чтобы не класть прод.
Почему обычный деплой — это риск
Классический сценарий: разработчик меняет /api/users — теперь поле name разбилось на first_name и last_name. Деплой в 18:00 пятницы. Мобильное приложение, которое ждёт name, начинает падать. Поддержка получает шквал жалоб.
Проблема не в том, что разработчик плохой. Проблема в том, что между «старым контрактом» и «новым контрактом» не было переходного периода. Клиенты не успели обновиться.
Миграция без даунтайма — это, по сути, управление переходным периодом. Вы не просто меняете API, вы даёте всем потребителям время адаптироваться.
Шаг первый: версионирование API
Самый очевидный инструмент — версии. Если у вас их нет, начните с этого.
Есть три популярных подхода:
URI versioning — версия в пути: /api/v1/users, /api/v2/users. Просто, видно в логах, легко кешировать. Самый распространённый вариант.
Header versioning — версия в заголовке: Accept: application/vnd.myapi.v2+json. Чище с точки зрения REST, но сложнее дебажить и тестировать вручную.
Query param versioning — /api/users?version=2. Работает, но смотрится некрасиво и путает кеши.
Для большинства проектов URI versioning — оптимальный выбор. Его понимают все: фронтенд, мобильные разработчики, сторонние партнёры.
Как только у вас есть /v1 и /v2, можно деплоить изменения в /v2, не трогая /v1. Клиенты переходят на новую версию в своём темпе.
GET /api/v1/users/42
→ { "name": "Иван Петров", "email": "ivan@example.com" }
GET /api/v2/users/42
→ { "first_name": "Иван", "last_name": "Петров", "email": "ivan@example.com" }
Обе версии живут одновременно. Когда 100% клиентов перешли на v2, v1 можно сначала пометить deprecated, потом отключить.
Шаг второй: backward-compatible изменения
Не каждое изменение требует новой версии. Если изменение обратно совместимо — его можно катить без версионирования.
Безопасные изменения:
- Добавить новое поле в ответ — клиенты, которые его не ожидают, просто проигнорируют
- Добавить необязательный параметр запроса
- Добавить новый эндпоинт
- Расширить enum новым значением (осторожно: если клиент делает switch/case без default — он упадёт)
Ломающие изменения:
- Удалить поле из ответа
- Переименовать поле
- Изменить тип поля (строка → число)
- Сделать необязательный параметр обязательным
- Изменить формат ошибок
- Изменить логику аутентификации
Для ломающих изменений — новая версия или expand-contract паттерн.
Expand-contract паттерн
Один из самых надёжных способов мигрировать без даунтайма — делать изменения в три этапа.
Expand (расширение): добавляем новое поле параллельно со старым. Теперь API возвращает и name, и first_name/last_name. Клиенты могут начинать переходить.
{
"name": "Иван Петров",
"first_name": "Иван",
"last_name": "Петров",
"email": "ivan@example.com"
}
Migrate (миграция): обновляем всех клиентов, чтобы они использовали новые поля.
Contract (сжатие): когда все перешли — удаляем старое поле name.
Между первым и третьим этапом может пройти неделя, месяц или полгода — зависит от того, сколько у вас клиентов и насколько быстро они обновляются.
Blue-green деплой
Версионирование решает проблему контракта. Blue-green деплой решает проблему самого деплоя — когда нужно переключить трафик без простоя.
Идея простая:
- Blue — текущая production-среда
- Green — новая версия приложения
Алгоритм:
- Поднимаете green-окружение с новой версией API
- Прогоняете smoke-тесты на green
- Переключаете балансировщик с blue на green (занимает секунды)
- Blue остаётся в живых как резерв для быстрого отката
Для реализации подходит любой балансировщик — Nginx, HAProxy, AWS ALB. Главное, что переключение трафика занимает не минуты, а секунды, и у вас всегда есть куда откатиться.
upstream api {
server green-api:8080; # переключаем сюда
# server blue-api:8080; # было
}
Минус: нужно держать два окружения одновременно — это удваивает расходы на инфраструктуру в момент деплоя. Для небольших проектов это может быть дорого.
Канареечные релизы
Если blue-green — это «всё или ничего», то canary deployment — постепенное переключение.
Вы направляете на новую версию сначала 1% трафика, потом 5%, 10%, 50% и наконец 100%. На каждом этапе смотрите на метрики: количество ошибок, латентность, бизнес-показатели.
Если что-то пошло не так — откатываете 1%, а не весь трафик.
Пример на Nginx:
upstream api_v2 {
server new-api:8080 weight=10; # 10% трафика
server old-api:8080 weight=90; # 90% трафика
}
На уровне Kubernetes это делается через weight в Ingress или через service mesh типа Istio:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
http:
- route:
- destination:
host: api-v2
weight: 10
- destination:
host: api-v1
weight: 90
Канареечный деплой особенно полезен для больших изменений, где сложно предсказать все последствия. Вы получаете реальный трафик на новую версию, но ограничиваете радиус поражения.
Feature flags для API
Фичевые флаги дают ещё больше контроля. Вместо инфраструктурного переключения трафика вы управляете поведением на уровне кода.
def get_user(user_id):
user = db.get_user(user_id)
if feature_flags.is_enabled('new_user_response', user_id):
return {
'first_name': user.first_name,
'last_name': user.last_name,
'email': user.email
}
else:
return {
'name': f"{user.first_name} {user.last_name}",
'email': user.email
}
Можно включать новый формат только для определённых пользователей, аккаунтов или процента запросов — без перезапуска сервиса.
Инструменты: LaunchDarkly, Flagsmith, собственная реализация через Redis. Для небольших проектов достаточно простой таблицы в базе данных.
Как управлять устаревшими версиями
Версии не могут жить вечно. Но и убивать их без предупреждения — грубо.
Хорошая практика — трёхэтапный deprecation:
1. Объявление: добавляете заголовок Deprecation в ответы устаревшей версии. RFC 8594 описывает стандарт:
Deprecation: Sun, 01 Jun 2026 00:00:00 GMT
Sunset: Sun, 01 Sep 2026 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"
2. Напоминания: пишете разработчикам, которые ещё используют v1. Если у вас есть аналитика запросов — вы знаете, кто это.
3. Отключение: возвращаете 410 Gone с понятным сообщением и ссылкой на документацию v2.
Минимальный срок поддержки deprecated-версии — 3 месяца для внутренних API, 6-12 месяцев для публичных.
Миграция базы данных как часть API-миграции
Часто изменение API требует изменения схемы БД. Это отдельная история, но её нельзя игнорировать.
Основной принцип: схема базы данных должна поддерживать и старую, и новую версию API в период миграции.
Пример: вы делите поле name на first_name и last_name в таблице users.
Опасный способ:
ALTER TABLE users DROP COLUMN name;
ALTER TABLE users ADD COLUMN first_name VARCHAR(100);
ALTER TABLE users ADD COLUMN last_name VARCHAR(100);
Безопасный способ (expand-contract для БД):
-- Этап 1: добавляем новые колонки, не удаляя старую
ALTER TABLE users ADD COLUMN first_name VARCHAR(100);
ALTER TABLE users ADD COLUMN last_name VARCHAR(100);
-- Заполняем новые колонки из старой
UPDATE users SET
first_name = SPLIT_PART(name, ' ', 1),
last_name = SPLIT_PART(name, ' ', 2);
-- Этап 2: мигрируем код
-- Этап 3: удаляем старую колонку, когда всё стабильно
ALTER TABLE users DROP COLUMN name;
Между этапами — время. Несколько дней минимум, лучше неделя-две.
Мониторинг во время миграции
Миграция без мониторинга — как деплой вслепую. Что нужно отслеживать:
Error rate по версиям. Если ошибки растут на v2 — что-то пошло не так. Разбивайте метрики по версиям API с самого начала.
Латентность. Новый эндпоинт может быть медленнее из-за дополнительных запросов к БД или новой логики.
Трафик по версиям. График должен показывать постепенное снижение v1 и рост v2. Если кто-то «застрял» на v1 — вы это увидите.
Бизнес-метрики. Конверсии, количество заказов, активные сессии. Технические метрики могут быть зелёными, а бизнес — падать из-за неочевидного бага в логике.
Минимальный стек для мониторинга: Prometheus + Grafana, либо любой APM — Datadog, New Relic, Sentry.
Как откатиться, если всё пошло не так
План отката должен быть готов до деплоя, а не после того, как всё упало.
Чек-лист для отката:
- Балансировщик переключён обратно? (blue-green упрощает это до одной команды)
- Миграции базы данных обратимы? (всегда пишите rollback-скрипты)
- Feature flags выключены?
- Команда оповещена?
Время отката — важнее его причины. Если что-то идёт не так, цель — вернуть стабильность за минуты, а разбираться в причинах — потом, в спокойной обстановке.
Реальный план миграции
Соберём всё в последовательность:
-
Аудит клиентов. Кто использует текущий API? Внутренние сервисы, мобильное приложение, сторонние партнёры?
-
Версионирование. Если его нет — добавить сейчас. Текущий API становится v1.
-
Реализация v2. Пишете новый API параллельно со старым. Expand-contract для базы данных.
-
Документация. Changelog, migration guide, дата отключения v1.
-
Деплой v2. Blue-green или канареечный. Начинаете с малого процента трафика.
-
Мониторинг. Минимум 2 недели наблюдаете за метриками обеих версий.
-
Миграция клиентов. Помогаете командам перейти на v2. Устанавливаете дедлайн для v1.
-
Deprecation. Заголовки, уведомления, напоминания.
-
Отключение v1. Через 3-6 месяцев после выхода v2.
Если вы только проектируете API с нуля или переписываете старый монолит — закладывайте версионирование сразу. Добавить его потом можно, но болезненно. В REEXY при разработке интеграций и API-сервисов это входит в стандартный процесс — чтобы клиент мог менять бэкенд без страха положить фронт или мобилку.
Миграция API — это не технический подвиг, а процесс. Чем лучше он описан и автоматизирован, тем спокойнее спите вы и все, кто зависит от вашего сервиса.