{ "index": 114, "slug": "editorial-2024-11-practice-deprecation", "title": "Депрекация API без аварии: карта потребителей и gate удаления", "excerpt": "Дата Sunset и пустой график вызовов не доказывают, что endpoint можно удалить. Показываю, как ограничить scope, собрать карту потребителей и остановить опасное удаление.", "contentHtml": "

В задаче на удаление API обычно есть убедительная дата: после 3 февраля старый путь должен исчезнуть. В день релиза график вызовов пуст, в OpenAPI стоит deprecated: true, а владельца старого клиента никто не знает. Обработчик удаляют. Через час внешний интегратор получает 404, а команда не может ответить, кто предупредил потребителя и где описан переход.

\n

Причина не в том, что календарь составили плохо. Дата задаёт план, но не доказывает отсутствие потребителей. Пустой график описывает только выбранный маршрут, период, прокси, credentials и качество телеметрии. Пометка в схеме сообщает о жизненном цикле операции, но не обновляет SDK и не доставляет уведомление. Без карты зависимостей удаление превращается в догадку.

\n

Рабочее правило: сначала докажите границы проверки, затем принимайте решение об удалении одной операции. Результат проверки может быть observed, not observed in scope или unknown. Последние два результата нельзя склеивать с фразой «никто не использует». При активном или неизвестном потребителе автоматический removal gate закрыт.

\n

Что именно мы собираемся удалить

\n

Фраза «убираем v1» слишком широка для безопасного изменения. Она может означать один метод, несколько ресурсов, SDK-функцию, webhook или всю версию API. Начните с одной операции и запишите её как контракт: HTTP-метод, URI-шаблон, operationId, форму запроса, ответы, коды ошибок и границу авторизации.

\n

Такой scope нужен не для бюрократии. Владелец сервиса может подтвердить удаление POST /v1/posting, но не всего /v1. Новый URL может принимать тот же JSON, но иначе обрабатывать повторную доставку, идемпотентность, пагинацию или права. Поэтому рядом с replacement фиксируйте семантические отличия. Совпадение URL и формата ещё не означает совместимость.

\n
Минимальный контракт removal proposal
ПолеЧто записатьПочему это ограничивает риск
OperationPOST /v1/posting и operationIdНе даёт распространить решение на соседние методы
ReplacementНовый метод, схема, ошибки и правила повторовПоказывает, что переход — не простая замена строки
PopulationИзвестные, внешние и неизвестные классы клиентовНе маскирует неполноту инвентаризации
Evidence boundaryИнструмент, окно, маршрут, credentials, sampling и пропускиОтделяет наблюдение от доказательства полноты
Restore boundaryЧто возвращается и кто проверяет восстановлениеПревращает rollback из обещания в проверяемое условие
\n

Почему одного сигнала недостаточно

\n

Карта потребителей должна разделять классы сведений. Поиск исходников показывает вызовы в просмотренной области. Manifest показывает объявленную зависимость. Access log показывает запросы, дошедшие до конкретного слоя. Сведения от владельца подтверждают намерение команды, но не заменяют runtime-проверку. У каждого сигнала свой blind spot, поэтому итоговая строка хранит и результат, и границу.

\n
Как читать отрицательный результат
РезультатЧто он действительно означаетЧего он не доказываетСледующее действие
source: foundВ просмотренном репозитории найден вызовЧто это единственный потребительНазначить владельца и запланировать миграцию
traffic: zeroЗа окном не было видимых запросовЧто нет batch, другого gateway или редкого клиентаРасширить окно и сверить маршрут и credentials
declared: absentЗависимость не записана в проверенном manifestЧто внешний binary или сгенерированный SDK отсутствуетПроверить inventory и канал объявления
scope: unknownПолнота области не доказанаЧто endpoint безопасно удалятьСохранить совместимость или принять риск отдельно
\n
Схема удаления API: операция связана с картой потребителей, владельцами, replacement и границами доказательств; active или unknown consumer ведёт к остановке удаления
Карта показывает порядок решения: сначала scope и evidence, затем миграция; неизвестная область закрывает автоматическое удаление. Имена на схеме условные.
\n

Например, отсутствие записи в репозитории не видит закрытый исходный код, сгенерированный клиент или старый бинарный клиент. Нулевой access log не видит запрос, который прошёл через другой hostname, не попал в выбранный gateway или пришёл раз в квартал. Даже полный на вид отчёт относится к своему окну и credential class. Именно поэтому строка unknown consumer полезнее пустой ячейки: она сохраняет незамкнутую область в решении.

\n

Депрекация не равна отключению

\n

В OpenAPI 3.1 поле deprecated у Operation Object объявляет операцию устаревшей и рекомендует потребителям прекратить её использование. Это описание контракта. Оно не ищет клиентов, не выпускает новую версию SDK и не меняет ответ сервера.

\n

Runtime-уведомление решает другую задачу. Заголовок Deprecation из RFC 9745 сообщает клиенту дату депрекации; его значение — structured date, например Deprecation: @1688169599. Тот же RFC подчёркивает: сам факт депрекации не меняет поведение ресурса. Для документации можно добавить Link с отношением deprecation, где указаны replacement и миграционная инструкция.

\n

Sunset из RFC 8594 сообщает, что ресурс ожидается недоступным после указанного HTTP-времени. Это подсказка для клиента, а не гарантия того, что до даты всё будет работать, а после неё обязательно появится конкретный код. В RFC 9745 также зафиксировано, что Sunset не должен быть раньше даты Deprecation. Проверяйте эти заголовки на фактическом ответе и не выдавайте их за доказательство миграции.

\n
BASE_URL=https://api.example.test; curl --fail-with-body -sS -D response.headers -o response.body \"$BASE_URL/v1/posting\"; awk 'BEGIN{IGNORECASE=1} /^deprecation:|^sunset:|^link:/{print}' response.headers
\n

Команда предназначена для собственного стенда: замените api.example.test адресом среды, где разрешена такая проверка, и не помещайте токены в shell history или публикацию. Если endpoint требует авторизацию, задайте её способом, принятым в проекте, и отдельно запишите, какой класс credentials был покрыт. Ответ одного proxy не подтверждает поведение другого слоя.

\n

Как собрать карту потребителей

\n

Минимальная запись содержит consumer, класс evidence, owner, replacement, migration path, срок, канал уведомления и evidence boundary. Для известного клиента нужна не только строка «migrated», а ссылка на проверку: версия SDK, тест совместимости, успешный запрос или подтверждённый rollout. Статус в таблице — это утверждение, его источник и область должны быть видны рядом.

\n
consumer,kind,owner,replacement,status,evidence_boundary; checkout-web,source-and-traffic,team-checkout,POST /v2/posting,migrating,repo=checkout; partner-batch,traffic-only,partner-team,POST /v2/posting,unknown,logs=gw-a; unknown-external,unknown,api-owner,none,block,public-route
\n

Это CSV-образец, а не список реальных клиентов. В рабочем процессе его можно хранить в системе, где есть история изменений и доступ владельцев. Не записывайте секреты, токены и персональные данные: для связи достаточно идентификатора системы, команды и ссылки на разрешённое доказательство.

\n

Отдельно проверяйте четыре направления: код и сгенерированные клиенты; реестр SDK и зависимостей; сетевые логи на ingress и gateway; коммуникацию с внешними владельцами. Дублируйте результат только там, где сигналы независимы. Три отчёта из одного прокси не превращаются в три доказательства.

\n

Removal gate должен уметь остановить изменение

\n

Хороший gate формулируется отрицательными условиями. Удаление запрещено, если найден активный consumer; если неизвестная область не ограничена и остаточный риск не принят; если replacement меняет значимую семантику без плана; если notice не связан с affected scope; если не описано восстановление. Ранняя дата не отменяет ни одного из этих условий.

\n

Разделяйте два решения. Первое — можно ли объявить операцию устаревшей и начать миграцию. Второе — можно ли менять runtime так, чтобы старый путь перестал отвечать. Между ними должны быть окно совместимости, подтверждённые владельцы и отдельное human review. Не объединяйте удаление route, изменение прав, чистку документации и удаление SDK в один непроверяемый коммит.

\n
Вердикты перед удалением
ВердиктУсловиеРазрешённое действие
block: activeЕсть запросы или подтверждённая зависимостьОстановить removal, назначить миграцию
block: unknownОбласть проверки не замкнутаРасширить наблюдение или продлить совместимость
allow: reviewScope, replacement, owners, notice и restore boundary описаныОткрыть отдельный review; не удалять автоматически
allow: changeПолитика проекта допускает изменение после reviewПрименить узкий change и проверить соседние операции
\n

Воспроизводимая последовательность

\n
  1. Зафиксируйте scope. Назовите одну operation, метод, URI, operationId, схемы ответа и authentication boundary.
  2. Опишите replacement. Сравните семантику, ошибки, идемпотентность, повторы, лимиты и права, а не только URL.
  3. Соберите карту. Проверьте source usage, manifests, SDK inventory, gateway и ingress. Добавьте отдельную строку unknown, если полнота не доказана.
  4. Разметьте evidence. Для каждого результата сохраните инструмент, окно, маршрут, credentials, sampling и blind spots.
  5. Назначьте владельцев. У каждой известной зависимости должны быть owner, migration path, версия replacement и способ подтвердить переход.
  6. Опубликуйте notice. Свяжите OpenAPI, Deprecation, Sunset, Link-документацию и миграционный план с тем же scope.
  7. Проверьте stop conditions. Active, unknown без принятого риска, несовместимый replacement и отсутствующий restore boundary закрывают gate.
  8. Откройте отдельный review. В решении укажите остаточный риск, затронутую operation и критерии отказа. Только после допуска создавайте runtime change.
  9. Сверьте результат. Повторите проверку после изменения: старый путь изменился ожидаемо, replacement доступен, а соседние методы и правила авторизации не затронуты.
\n

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

\n

Эта схема не делает неизвестных клиентов видимыми автоматически. Она не заменяет договор с внешним партнёром, анализ закрытого кода, юридические сроки уведомления, требования к доступности или политику change management. Конкретное окно наблюдения и достаточная полнота зависят от трафика: для ежедневного запроса 30 дней может быть полезным сигналом, для квартального batch — нет.

\n

Заголовки тоже имеют границы. Клиент может их не читать, промежуточный кеш может изменить наблюдаемую картину, а Deprecation и Sunset не доказывают, что владелец клиента получил notice. RFC 8594 не определяет, будет ли после Sunset 4xx, 3xx или другой отказ. Поэтому не стройте единственную защиту на runtime-заголовке и не обещайте точный код ответа, если он не закреплён вашим контрактом.

\n

Если потребитель активен, безопасное действие — остановить удаление и дать ему совместимый путь. Если потребитель неизвестен, безопасное действие — расширить scope или сохранить endpoint. Residual risk можно принять только тем владельцем и в том процессе, которые отвечают за последствия; запись «вроде никто не использует» таким решением не является.

\n

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

\n

Операция готова к removal review, когда её scope однозначен, replacement проверен по семантике, известные потребители имеют владельцев и миграционные доказательства, неизвестная область либо закрыта, либо явно принята, а notice и restore boundary доступны. Решение должно быть воспроизводимым: другой инженер может повторить запрос, понять границу логов, найти источник строки карты и назвать условие остановки.

\n

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

\n

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

\n" }