{ "index": 112, "slug": "editorial-2024-11-field-deprecation", "title": "Как удалить устаревший API и не сломать последнего клиента", "excerpt": "Депрекация не доказывает, что endpoint больше не используют. Разбираем removal gate: как отделить активного клиента от неизвестной зоны, выбрать проверку, остановить удаление и определить готовность изменения.", "contentHtml": "

Команда пометила GET /v1/orders/{id} устаревшим, выпустила замену и увидела ноль вызовов в выбранном графике. Удаление кажется безопасным, пока внешний интегратор не получает 404 или 410. Такой сбой начинается не с плохого HTTP-кода, а с неверного вывода: отсутствие записей в одном источнике приняли за отсутствие всех потребителей.

\n

Удаление API нужно рассматривать как доказательство с ограниченной областью, а не как дату в календаре. Сначала зафиксируйте одну операцию, затем свяжите известные вызовы с владельцами, явно запишите слепые зоны и только после этого откройте отдельный removal review. Если остался активный или неизвестный потребитель, автоматическое удаление должно остановиться.

\n

Депрекация не равна удалению

\n

В OpenAPI 3.1.0 поле deprecated: true объявляет операцию устаревшей и рекомендует потребителям перейти с неё. Это часть описания контракта. Поле не читает access log, не знает о старых версиях SDK и не подтверждает, что replacement поддерживает тот же сценарий. Если генератор документации не публикует это поле или использует другую версию OpenAPI, поведение нужно проверить в конкретном toolchain.

\n

Заголовок Sunset решает другую задачу. RFC 8594 описывает его как сигнал о том, что URI, вероятно, станет недоступен в указанный момент. Документ прямо называет timestamp подсказкой: после этой даты возможны ошибки, редирект или полная недоступность, но точное поведение не обещано. Поэтому Sunset помогает потребителю спланировать миграцию, но не заменяет inventory и проверку владельца.

\n

После удаления ответы тоже нужно трактовать аккуратно. RFC 9110 описывает 404 как отсутствие текущего представления или нежелание раскрывать его существование. 410 означает, что доступ больше недоступен и состояние, вероятно, постоянно. Если сервер не знает, что удаление окончательно, RFC рекомендует 404. Ни один статус сам по себе не доказывает, что все клиенты перешли на новый маршрут.

\n

Сначала сузьте предмет проверки

\n

Фраза «удаляем v1» слишком широкая для решения. В одной версии могут жить разные методы, форматы ошибок и права доступа. Для removal gate запишите method, URI template, operationId, версию схемы, replacement, владельца и дату, после которой разрешён отдельный change. Если меняется только поле ответа, проверяйте поле, а не весь маршрут.

\n

Сравните не только URL. Для каждого обязательного сценария зафиксируйте authentication boundary, коды ошибок, pagination, идемпотентность, лимиты и правила повторной отправки. Клиент может успешно получить 200 от нового маршрута и всё равно сломаться, если новый контракт иначе трактует пустой результат или отказ в доступе.

\n
Быстрый разбор перед удалением одной операции
НаблюдениеЧто оно доказываетЧего не доказываетСледующее действие
deprecated: true в OpenAPIОперация объявлена устаревшейПотребители мигрировалиОпубликовать replacement и начать consumer map
В графике нет запросовВ выбранном scope вызовы не наблюдалисьВызовов нет вообщеЗаписать период, sampling, регион и credential scope
Все найденные rows migratedНазванные потребители имеют путь переходаСписок потребителей полныйПроверить unknown-зону и назначить остаточный риск
Новая операция отвечает 200Один проверенный запрос прошёлСовместимы ошибки, права и редкие сценарииСравнить фиксированный набор contract cases
Назначена дата SunsetЕсть объявленная граница планированияРесурс станет недоступен точно в эту датуСогласовать отдельный removal change и stop condition
\n

Consumer map хранит найденное и неизвестное

\n

Строка consumer map должна связывать потребителя не с догадкой, а с evidence. Минимальный набор полей: имя потребителя, состояние, источник, период, охват, владелец, replacement и следующий шаг. Состояние active означает наблюдаемый вызов старого контракта. migrated означает, что названный потребитель перешёл и это подтверждено проверкой. unknown означает, что область не наблюдается или не проверена.

\n

Unknown — не мягкая форма migrated. Если telemetry не включает внешний tenant, старые credentials, редкий cron или трафик через общий gateway, строка должна остаться неизвестной. Запись «usage = 0» без периода и границы выглядит точной, но не позволяет воспроизвести вывод. За unknown назначают владельца остаточного риска: он либо расширяет разрешённую проверку, либо оставляет совместимость, либо выносит принятие риска на явное human review.

\n
\"Схема
Removal gate разделяет активный, неизвестный и мигрированный потребитель. Иллюстрация показывает логику решения, но не является телеметрией конкретного API.
\n

Правило removal gate

\n

Разделите решение на три ветки. Любой active блокирует удаление: сначала нужен владелец и согласованный путь миграции. Любой unknown блокирует автоматическое удаление: неизвестность не равна нулевой активности. Если все названные строки migrated, можно открыть ручной review, но это ещё не разрешение удалить маршрут. В review должны попасть контракт, owner, notice, residual risk и план проверки после релиза.

\n

Простейшее правило можно повторить локально на фиксированном наборе состояний. Команда ниже ничего не читает из production и не делает сетевых запросов: она только показывает, что unknown должен привести к блокировке.

\n
node --input-type=module -e \"const evidence = [{ state: 'migrated' }, { state: 'unknown' }]; const blocked = evidence.some(function (row) { return row.state === 'active' || row.state === 'unknown'; }); console.log(blocked ? 'block-removal' : 'human-review');\"\n\n// Ожидаемый вывод:\n// block-removal\n
\n

В учебной записи один известный сервис мигрировал, но внешняя зона не проверена. Поэтому автоматический результат — block-removal. Поле blindZone объясняет причину отказа. В реальном проекте замените фиктивные rows на записи из разрешённых источников и сохраните ссылку на запрос, dashboard или commit, который можно открыть повторно.

\n

Проверка по слоям

\n
  1. Контракт. Найдите операцию по operationId и URI, затем проверьте request, response, ошибки, auth и replacement. Для локального репозитория начните с команды rg -n 'getOrderV1|/v1/orders' .. Поиск показывает строки, но не доказывает runtime-вызов.
  2. Зависимости. Проверьте исходники, lock-файлы, сгенерированные клиенты, документацию и конфигурацию gateway. Отдельно ищите динамически собранные URL и старые версии пакетов.
  3. Наблюдаемость. Возьмите запросы за заранее выбранный период и запишите route, client identity, регион, tenant, sampling, кеши и очереди. SQL-шаблон SELECT client_id, count(*) FROM api_access WHERE route = '/v1/orders/{id}' AND ts >= :from AND ts < :to GROUP BY client_id; нужно адаптировать к своей схеме; результат без описания охвата остаётся частичным.
  4. Владельцы. Для каждой найденной строки назначьте человека или команду, срок миграции, replacement и способ связаться. Внешнего потребителя нельзя считать migrated по тому, что внутренний сервис собрался.
  5. Совместимость. Прогоните одинаковый фиксированный набор сценариев против v1 и v2 в тестовой среде. Сравните обязательные поля, коды ошибок, права, pagination, ретраи и побочные эффекты. Один успешный happy path не закрывает контракт.
  6. Уведомление. Обновите OpenAPI, migration guide и changelog. Если используете Sunset, явно укажите scope и трактуйте дату как сигнал, а не гарантию доступности или удаления.
  7. Stop condition. Заранее запишите факты, при которых review прекращается: active row, unresolved unknown, несовместимая ошибка, неподтверждённый owner или отсутствие способа восстановить старый контракт.
  8. Отдельное изменение. Удаление оформите самостоятельным change с наблюдением после релиза. Не прячьте его внутри миграции, чтобы зелёные тесты нового клиента не замаскировали старого.
\n

Restore boundary важнее обещания отката

\n

До удаления назовите последний совместимый контракт и способ временно вернуть обработчик. Если новый код уже изменяет данные, восстановление HTTP-маршрута не вернёт прежнее состояние. Тогда граница восстановления должна включать совместимый read path, миграцию данных или заранее подготовленный обратный план.

\n

Согласуйте, кто может остановить rollout и какой сигнал это делает. Например, рост ответов 4xx от известного клиента после удаления — достаточное основание остановить change, но не доказательство, что причина именно в API. Нужны correlation id, журнал изменения и сравнение с baseline. Откат должен вернуть систему в проверяемое состояние, а не просто скрыть симптом.

\n

После удаления проверяется не только статус

\n

Проверка после релиза должна охватить старый и новый путь в заявленной области. Для старого маршрута проверьте ожидаемый статус и тело ошибки тестовым клиентом, не используя реальные секреты. Для нового — повторите контрактные сценарии и убедитесь, что авторизация, лимиты и ответы соответствуют согласованному replacement.

\n

Затем посмотрите на реальные сигналы: ошибки по client identity, retry rate, latency, gateway responses, очереди и обращения владельцев. Учитывайте задержку доставки логов и кэш. Если removal change коснулся только одного региона или credential scope, честный итог звучит так: «в заявленной области старый путь недоступен». Формулировка «клиентов не осталось» требует более сильного доказательства, чем обычно может дать один график.

\n

Ограничения применимости

\n

Полного доказательства отсутствия всех consumers обычно не существует. Редкий cron не попадёт в короткое окно. Динамический URL может исчезнуть из статического поиска. Общий gateway скроет исходного клиента. Другой tenant, регион, credential или sampling оставит blind zone. Кэш, retry и очередь сдвинут вызов за пределы момента проверки.

\n

Поэтому removal gate — это контроль риска, а не математическое доказательство. Он работает, когда у команды есть право читать нужные источники и назначать владельцев. При отсутствии доступа состояние остаётся unknown. Учебные rows и команда выше не являются фактическими данными, incident report или результатом запроса к вашему API.

\n

Критерий готовности

\n

Операция готова к отдельному removal change, если одновременно выполнены пять условий: scope операции и replacement однозначны; OpenAPI и уведомление опубликованы; каждая известная зависимость имеет owner и состояние; неизвестные области перечислены с периодом и stop condition; restore boundary проверена на совместимом контракте. Для каждого пункта оставьте ссылку на evidence.

\n

Если хотя бы один пункт не выполнен, решение не должно превращаться в «удалим и посмотрим». Оставьте старую операцию, ограничьте новые зависимости и назначьте следующий проверяемый шаг. Депрекация тогда становится управляемым переходом, а не бессрочной пометкой, которая однажды внезапно превращается в 404.

\n

Проверяемые источники

\n" }