{ "index": 113, "slug": "editorial-2024-11-mechanism-deprecation", "title": "Deprecation API: почему пустая telemetry не разрешает удаление", "excerpt": "Пустой график вызовов не доказывает отсутствие потребителей API. Разбираем пять типов evidence, границы deprecation и removal gate на ограниченном примере.", "contentHtml": "

Команда собирается удалить старый endpoint. За последние две недели в dashboard нет вызовов. В access log видны только запросы к новой версии. Кто-то делает вывод: потребителей больше нет.

\n

Симптом выглядит убедительно, но он описывает только выбранный источник наблюдения. В него могли не попасть редкие клиенты, другой gateway, кэш, batch-задача или credential с отдельной политикой доступа. Цена ошибки — несовместимый релиз для неизвестного caller. Ошибка проявится после удаления, когда старый контракт уже не с чем сравнивать.

\n

Тезис статьи простой: deprecation — это управляемый переход, а не доказательство отсутствия пользователей. Сначала нужно разделить типы сведений и их границы. Затем объявить замену, предупредить потребителей, определить sunset boundary и только после отдельной проверки открыть removal gate. Ни один сигнал сам по себе не превращает пустую telemetry в полный список клиентов.

\n

Что именно нужно доказать

\n

Начните с одной операции: method, URI template, operationId и версия контракта. Формулировка «удаляем старый API» слишком широка. Для POST /v1/ledger/entries вопрос звучит точнее: какие callers ещё зависят от поведения этой операции, кто отвечает за замену и какие наблюдения покрывают маршрут?

\n

Source usage показывает вызов в заданном дереве исходников. Он помогает найти известный сервис, но не видит закрытый репозиторий, скомпилированный клиент или deployed версию, которая не совпала с локальной веткой. Declared dependency показывает объявленный SDK, schema или contract. Зависимость может остаться в manifest после миграции и не доказывает runtime-вызов.

\n

Exposure or traffic показывает observed requests в конкретном инструменте, маршруте, периоде и sampling policy. Запрос не равен пользователю. Один сервис может отправить тысячу запросов, а редкий клиент — один запрос за месяц. Нулевое значение означает «в этом scope сигнал не найден», а не «caller отсутствует».

\n

Authorization показывает, какой credential class или permission способен обратиться к resource. Способность не равна активности. Если политика допускает партнёрский ключ, это ещё не доказывает, что партнёр вызывает операцию. Но если такой класс не учтён, removal имеет слепую зону.

\n

Unknown consumer — не ошибка заполнения таблицы. Это честное состояние, когда scope нельзя замкнуть. Его нужно хранить отдельно от zero и migrated. Именно unknown должен блокировать автоматическое решение об удалении.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
График вызовов равен нулюОкно или route не покрывает всех callersЗаписать инструмент, период, sampling, cache и auth boundaryОставить consumer unknown и расширить разрешённую проверку
В manifest осталась зависимостьDeclared dependency приняли за runtime usageСверить версию, owner и фактический call pathНазначить migration owner, не удалять endpoint по одной записи
В OpenAPI стоит deprecatedДекларацию приняли за завершённую миграциюНайти replacement и проверить его контрактОпубликовать warning и сохранить совместимое поведение
Наступила дата SunsetДату приняли за доказательство, что все ушлиПровести removal review по отдельному scopeОстановить change при active или unknown row
\n

Четыре границы lifecycle

\n

Warning делает замену видимой. Документация должна назвать replacement, owner, область действия и способ задать вопрос. Warning не подтверждает, что клиент его прочитал. Поэтому он не меняет функциональное поведение endpoint.

\n

Deprecation сообщает, что операция больше не является предпочтительной. В OpenAPI это поле deprecated: true. В HTTP можно использовать Deprecation header и ссылку на документацию, если такой сигнал поддерживают ваши клиенты. Эти механизмы помогают обнаружить новые зависимости, но не выключают ресурс и не перечисляют callers.

\n

Sunset задаёт планируемую границу возможной недоступности. RFC 8594 описывает её как сигнал о том, что конкретный URI, вероятно, станет недоступен в указанное время. Это hint, а не доказательство миграции и не гарантия, что сервер исчезнет ровно в timestamp. До этой границы всё равно нужна проверка остаточного риска.

\n

Removal — отдельное несовместимое изменение. Оно должно иметь узкий scope, owner, stop condition и restore boundary. Если одна строка consumer map имеет состояние active или unknown, автоматическое удаление нельзя считать безопасным. Состояние migrated разрешает только review: нужно проверить замену, данные и поведение, а не просто закрыть старый маршрут.

\n
\"Временная
Сигналы идут последовательно, но не заменяют друг друга. Иллюстрация показывает порядок решений; она не содержит telemetry и не задаёт реальную календарную дату.
\n

Учебный пример: одна операция и три состояния

\n

Ниже приведён синтетический пример. Имена, даты и строки не получены из production, telemetry или списка клиентов. Они нужны, чтобы показать логику решения на одном endpoint.

\n
operation: POST /v1/ledger/entries\nreplacement: POST /v2/ledger/entries\nowner: ledger-team\nsunset: 2025-02-03\n\nconsumer              evidence             state\ncheckout-service      source usage         active\nmobile-sdk             declared dependency migrated\npartner-gateway       authorization       unknown\n\nremoval: block\nreason: active and unknown consumers remain
\n

Первая строка содержит наблюдаемый вызов. Она блокирует removal. Вторая подтверждает план перехода, но сама по себе не доказывает, что старый endpoint больше не вызывается. Третья показывает capability boundary: gateway может иметь доступ, однако его активность не установлена. Вердикт должен остаться block.

\n

Если заменить значение unknown на zero, факты не изменятся. Изменится только видимость риска. Поэтому consumer map должна хранить не только state, но и evidence scope: tool, period, route, sampling, auth boundary, owner и blind zone. Без этих полей строка не воспроизводится.

\n

Порядок действий

\n
  1. Зафиксируйте одну операцию и точный replacement. Не объединяйте в одну карту разные URI, версии и semantics.
  2. Назначьте owner старого контракта и owner замены. Запишите, кто может остановить change.
  3. Добавьте warning в документацию и migration guide. Опишите совместимость, различия ответов и путь поддержки.
  4. Объявите deprecation в OpenAPI или согласованным HTTP-сигналом. Не меняйте поведение endpoint только из-за notice.
  5. Соберите evidence по отдельным типам: source usage, declared dependency, exposure or traffic, authorization и unknown. Для каждой записи укажите scope и слепую зону.
  6. Проверьте replacement на тех же входах и ошибках, которые важны для старого контракта. «Клиент обновился» недостаточно без проверки поведения.
  7. Назначьте sunset boundary как плановую дату и заранее определите stop condition. Active row, unknown row или несовместимый replacement должны останавливать удаление.
  8. Проведите human review removal. Решение должно содержать residual risk, restore boundary и ссылку на evidence. Только затем выполняйте отдельный change.
\n

Отрицательный путь

\n

Иногда все named consumers мигрировали, а неизвестный класс доступа остался. Это не повод объявить карту полной. Сохраните старый контракт совместимым, сузьте следующую проверку до разрешённой auth boundary и назначьте срок пересмотра. Если нужное наблюдение нельзя получить законно или технически, риск остаётся unknown. Дата Sunset не превращает его в zero.

\n

Другой отрицательный путь — replacement меняет semantics: новые обязательные поля, другой порядок побочных эффектов или иные коды ошибок. Даже полный список callers не делает такое удаление безопасным. Сначала нужно сравнить контракты и решить, как клиент переживёт несовместимость. Если restore невозможен после записи новых данных, rollback старого route не восстановит прежнее состояние.

\n

Ограничения

\n

Эта схема не создаёт универсальный consumer map. Она не отменяет sampling, кэширование, batch-вызовы, закрытые сети, задержку доставки логов и различия между deployed и исходным кодом. Она также не говорит, сколько дней нужно наблюдать. Окно зависит от частоты вызовов и допустимого риска, а его границы нужно обосновать.

\n

Учебный код выше не вызывает сеть, не читает файлы и не утверждает production-результаты. Официальные спецификации описывают смысл сигналов, но не проверяют ваш API. Реальную готовность устанавливает только evidence с понятным scope и ответственным владельцем.

\n

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

\n

Removal готов к отдельному рассмотрению, если выполнены все условия: одна операция имеет названный replacement; у старого и нового контрактов есть owners; warning и deprecation опубликованы; каждая строка consumer map содержит тип evidence, scope, период и blind zone; active и unknown строки либо закрыты разрешённой проверкой, либо явно приняты владельцем как residual risk; replacement проверен на критичных входах; определены stop condition и restore boundary. Если хотя бы одно условие неизвестно, вердикт — block, а не «пользователей нет».

\n

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

" }