{ "index": 114, "slug": "editorial-2024-11-practice-deprecation", "title": "Депрекация API без аварии: карта потребителей и gate удаления", "excerpt": "Дата Sunset и пустой график вызовов не доказывают, что endpoint можно удалить. Показываю, как ограничить scope, собрать карту потребителей и остановить опасное удаление.", "contentHtml": "
В задаче на удаление API обычно есть убедительная дата: после 3 февраля старый путь должен исчезнуть. В день релиза график вызовов пуст, в OpenAPI стоит deprecated: true, а владельца старого клиента никто не знает. Обработчик удаляют. Через час внешний интегратор получает 404, а команда не может ответить, кто предупредил потребителя и где описан переход.
Причина не в том, что календарь составили плохо. Дата задаёт план, но не доказывает отсутствие потребителей. Пустой график описывает только выбранный маршрут, период, прокси, credentials и качество телеметрии. Пометка в схеме сообщает о жизненном цикле операции, но не обновляет SDK и не доставляет уведомление. Без карты зависимостей удаление превращается в догадку.
\nРабочее правило: сначала докажите границы проверки, затем принимайте решение об удалении одной операции. Результат проверки может быть observed, not observed in scope или unknown. Последние два результата нельзя склеивать с фразой «никто не использует». При активном или неизвестном потребителе автоматический removal gate закрыт.
Фраза «убираем v1» слишком широка для безопасного изменения. Она может означать один метод, несколько ресурсов, SDK-функцию, webhook или всю версию API. Начните с одной операции и запишите её как контракт: HTTP-метод, URI-шаблон, operationId, форму запроса, ответы, коды ошибок и границу авторизации.
Такой scope нужен не для бюрократии. Владелец сервиса может подтвердить удаление POST /v1/posting, но не всего /v1. Новый URL может принимать тот же JSON, но иначе обрабатывать повторную доставку, идемпотентность, пагинацию или права. Поэтому рядом с replacement фиксируйте семантические отличия. Совпадение URL и формата ещё не означает совместимость.
| Поле | Что записать | Почему это ограничивает риск |
|---|---|---|
| Operation | POST /v1/posting и operationId | Не даёт распространить решение на соседние методы |
| Replacement | Новый метод, схема, ошибки и правила повторов | Показывает, что переход — не простая замена строки |
| Population | Известные, внешние и неизвестные классы клиентов | Не маскирует неполноту инвентаризации |
| Evidence boundary | Инструмент, окно, маршрут, credentials, sampling и пропуски | Отделяет наблюдение от доказательства полноты |
| Restore boundary | Что возвращается и кто проверяет восстановление | Превращает rollback из обещания в проверяемое условие |
Карта потребителей должна разделять классы сведений. Поиск исходников показывает вызовы в просмотренной области. Manifest показывает объявленную зависимость. Access log показывает запросы, дошедшие до конкретного слоя. Сведения от владельца подтверждают намерение команды, но не заменяют runtime-проверку. У каждого сигнала свой blind spot, поэтому итоговая строка хранит и результат, и границу.
\n| Результат | Что он действительно означает | Чего он не доказывает | Следующее действие |
|---|---|---|---|
source: found | В просмотренном репозитории найден вызов | Что это единственный потребитель | Назначить владельца и запланировать миграцию |
traffic: zero | За окном не было видимых запросов | Что нет batch, другого gateway или редкого клиента | Расширить окно и сверить маршрут и credentials |
declared: absent | Зависимость не записана в проверенном manifest | Что внешний binary или сгенерированный SDK отсутствует | Проверить inventory и канал объявления |
scope: unknown | Полнота области не доказана | Что endpoint безопасно удалять | Сохранить совместимость или принять риск отдельно |
Например, отсутствие записи в репозитории не видит закрытый исходный код, сгенерированный клиент или старый бинарный клиент. Нулевой access log не видит запрос, который прошёл через другой hostname, не попал в выбранный gateway или пришёл раз в квартал. Даже полный на вид отчёт относится к своему окну и credential class. Именно поэтому строка unknown consumer полезнее пустой ячейки: она сохраняет незамкнутую область в решении.
В OpenAPI 3.1 поле deprecated у Operation Object объявляет операцию устаревшей и рекомендует потребителям прекратить её использование. Это описание контракта. Оно не ищет клиентов, не выпускает новую версию SDK и не меняет ответ сервера.
Runtime-уведомление решает другую задачу. Заголовок Deprecation из RFC 9745 сообщает клиенту дату депрекации; его значение — structured date, например Deprecation: @1688169599. Тот же RFC подчёркивает: сам факт депрекации не меняет поведение ресурса. Для документации можно добавить Link с отношением deprecation, где указаны replacement и миграционная инструкция.
Sunset из RFC 8594 сообщает, что ресурс ожидается недоступным после указанного HTTP-времени. Это подсказка для клиента, а не гарантия того, что до даты всё будет работать, а после неё обязательно появится конкретный код. В RFC 9745 также зафиксировано, что Sunset не должен быть раньше даты Deprecation. Проверяйте эти заголовки на фактическом ответе и не выдавайте их за доказательство миграции.
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 не подтверждает поведение другого слоя.
Минимальная запись содержит consumer, класс evidence, owner, replacement, migration path, срок, канал уведомления и evidence boundary. Для известного клиента нужна не только строка «migrated», а ссылка на проверку: версия SDK, тест совместимости, успешный запрос или подтверждённый rollout. Статус в таблице — это утверждение, его источник и область должны быть видны рядом.
\nconsumer,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Хороший gate формулируется отрицательными условиями. Удаление запрещено, если найден активный consumer; если неизвестная область не ограничена и остаточный риск не принят; если replacement меняет значимую семантику без плана; если notice не связан с affected scope; если не описано восстановление. Ранняя дата не отменяет ни одного из этих условий.
\nРазделяйте два решения. Первое — можно ли объявить операцию устаревшей и начать миграцию. Второе — можно ли менять runtime так, чтобы старый путь перестал отвечать. Между ними должны быть окно совместимости, подтверждённые владельцы и отдельное human review. Не объединяйте удаление route, изменение прав, чистку документации и удаление SDK в один непроверяемый коммит.
\n| Вердикт | Условие | Разрешённое действие |
|---|---|---|
block: active | Есть запросы или подтверждённая зависимость | Остановить removal, назначить миграцию |
block: unknown | Область проверки не замкнута | Расширить наблюдение или продлить совместимость |
allow: review | Scope, replacement, owners, notice и restore boundary описаны | Открыть отдельный review; не удалять автоматически |
allow: change | Политика проекта допускает изменение после review | Применить узкий change и проверить соседние операции |
Эта схема не делает неизвестных клиентов видимыми автоматически. Она не заменяет договор с внешним партнёром, анализ закрытого кода, юридические сроки уведомления, требования к доступности или политику change management. Конкретное окно наблюдения и достаточная полнота зависят от трафика: для ежедневного запроса 30 дней может быть полезным сигналом, для квартального batch — нет.
\nЗаголовки тоже имеют границы. Клиент может их не читать, промежуточный кеш может изменить наблюдаемую картину, а Deprecation и Sunset не доказывают, что владелец клиента получил notice. RFC 8594 не определяет, будет ли после Sunset 4xx, 3xx или другой отказ. Поэтому не стройте единственную защиту на runtime-заголовке и не обещайте точный код ответа, если он не закреплён вашим контрактом.
Если потребитель активен, безопасное действие — остановить удаление и дать ему совместимый путь. Если потребитель неизвестен, безопасное действие — расширить scope или сохранить endpoint. Residual risk можно принять только тем владельцем и в том процессе, которые отвечают за последствия; запись «вроде никто не использует» таким решением не является.
\nОперация готова к removal review, когда её scope однозначен, replacement проверен по семантике, известные потребители имеют владельцев и миграционные доказательства, неизвестная область либо закрыта, либо явно принята, а notice и restore boundary доступны. Решение должно быть воспроизводимым: другой инженер может повторить запрос, понять границу логов, найти источник строки карты и назвать условие остановки.
\nИтогом не обязательно будет удаление. Иногда лучший результат — продлить совместимость, добавить телеметрию или сузить контракт. Дата в календаре становится полезной только после того, как рядом появились scope, доказательство, владелец и понятный следующий шаг.
\ndeprecated объявляет операцию устаревшей и не описывает список потребителей.deprecation, связь с документацией и границы runtime-сигнала. RFC опубликован в 2025 году, поэтому для процесса, зафиксированного раньше, отдельно проверьте поддерживаемый набор заголовков.