{ "index": 112, "slug": "editorial-2024-11-field-deprecation", "title": "Как удалить устаревший API и не сломать последнего клиента", "excerpt": "Депрекация не доказывает, что endpoint больше не используют. Разбираем removal gate: как отделить активного клиента от неизвестной зоны, выбрать проверку, остановить удаление и определить готовность изменения.", "contentHtml": "
После миграции старого endpoint команда видит зелёные тесты, пустой список известных клиентов и открывает удаление. В следующем релизе один интегратор получает 404 или 410. Его не нашли, потому что поиск прошёл только по репозиторию, а клиент жил в другом аккаунте, в старой версии SDK или за пределами выбранных логов. Цена ошибки — не только один сбой. Команда теряет совместимость, получает срочный откат и уже не может точно сказать, какую область проверила.
\nПроблема начинается с неверного вопроса: «кто последний consumer?». Полный список потребителей часто недостижим. Рабочий вопрос уже: «какие условия допускают удаление этого ресурса, что осталось неизвестным и какое наблюдение остановит change?». Это removal gate — отдельная проверка перед удалением. Она не обещает отсутствие скрытых клиентов. Она делает риск ограниченным, видимым и управляемым.
\nСначала зафиксируйте один ресурс. Это может быть GET /v1/orders/{id}, операция с конкретным operationId или поле ответа в версии контракта. Не называйте предметом проверки «старый API» целиком. У разных маршрутов будут разные владельцы, клиенты и сроки.
Депрекация меняет статус ресурса, но не должна незаметно менять его поведение. В OpenAPI поле deprecated: true сообщает о статусе операции. HTTP-заголовок Deprecation сообщает тот же сигнал во время запроса. Ссылка через Link может вести к описанию причины и замены. Ни один из этих сигналов не доказывает, что клиент прочитал уведомление и перешёл на новый маршрут.
Sunset тоже не является доказательством. Он обозначает ожидаемую границу, после которой ресурс может стать недоступным. Это дата для миграционного плана, а не подтверждение, что все callers уже ушли. Между уведомлением и удалением нужен отдельный decision.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Все найденные клиенты migrated | Список известных строк приняли за полную population | Назвать scope источника, период и blind zone | Оставить unknown и заблокировать автоматическое удаление |
| Трафик равен нулю | Проверка не видит нужный регион, credential или кэш | Сверить охват telemetry с ресурсом и клиентами | Расширить разрешённую проверку или сохранить совместимость |
Есть Deprecation и дата Sunset | Сигнал перепутали с фактом миграции | Проверить replacement, доставку notice и статус каждого клиента | Продолжить миграцию; removal gate не закрывать |
| Нашёлся active consumer | Владелец начал change до согласования последнего клиента | Проверить owner, контракт и путь перехода | Остановить удаление и вернуть задачу на миграцию |
| Неясно, как откатить change | Restore boundary не описали до удаления | Назвать последний совместимый контракт и stop condition | Не начинать removal change |
Разделите результат на три состояния. Active означает, что проверка нашла действующий вызов или зависимость. Unknown означает, что область не наблюдается или её нельзя проверить в разрешённом scope. Migrated означает, что названная зависимость перешла на replacement. Эти слова описывают разные факты. Нельзя превратить unknown в migrated только потому, что известные строки уже закрыты.
\nActive сразу блокирует удаление. У него должен быть владелец, способ связаться с ним и новый контракт. Unknown тоже блокирует автоматическое удаление, но по другой причине: неизвестность не равна нулевой активности. Для неё нужен владелец остаточного риска и конкретное решение — расширить проверку, продлить поддержку или принять ограниченный риск на human review. Migrated допускает подготовку предложения, но не означает, что маршрут можно удалить без отдельного change.
\nКаждая строка evidence должна отвечать на пять вопросов: какой ресурс проверяли, каким инструментом, за какой период, в какой области и чего инструмент не видит. Запись «usage = 0» без этих полей слаба. Она выглядит точной, но не объясняет, что именно измерено.
\nНиже приведён ограниченный учебный пример. Он не читает access log, код, сеть, CI или production и не возвращает реальные данные. Его задача — показать форму записи, в которой неизвестная зона остаётся явной.
\nconst review = {\n resource: {\n method: 'GET',\n path: '/v1/orders/{id}',\n operationId: 'getOrderV1',\n replacement: 'GET /v2/orders/{id}'\n },\n evidence: [\n {\n state: 'migrated',\n subject: 'checkout-service',\n source: 'dependency inventory',\n scope: 'repository set A, reviewed 2024-11-20',\n blindZone: 'runtime clients outside set A'\n },\n {\n state: 'unknown',\n subject: 'external integrations',\n source: 'not checked',\n scope: 'none',\n blindZone: 'all external credentials'\n }\n ],\n decision: 'block-removal',\n stopCondition: 'any active row or unresolved unknown scope',\n restoreBoundary: 'keep v1 route until approved removal change'\n};\nВ этом примере первый consumer migrated, но второй остаётся unknown. Поэтому итог — block-removal. Поле blindZone не украшает отчёт. Оно показывает, почему у команды нет права назвать результат полным. Если для unknown нельзя назвать следующую разрешённую проверку, риск нужно принять явно или сохранить старый контракт.
Deprecation, проверьте scope заголовка. Notice не должен менять semantics ответа.Sunset и объясните, что это ожидаемая дата возможной недоступности. Не выдавайте её за гарантию миграции.Хороший gate часто заканчивается отказом. Это не ошибка процесса. Если есть active row, команда получает конкретную работу по миграции. Если есть unknown, команда не маскирует пробел красивым нулём. Если replacement меняет поля, коды ошибок или порядок авторизации, старый маршрут остаётся до согласования совместимости.
\nОпасный путь выглядит иначе: поиск вернул пусто, в отчёте написали «клиентов нет», дату sunset приняли за дедлайн, а удаление объединили с миграцией. Такой результат нельзя воспроизвести и нельзя честно откатить. Пустой результат — это только утверждение инструмента в его границах.
\nНи один источник не даёт универсального способа доказать отсутствие всех consumers. Логи могут не охватить редкий вызов. Dependency inventory не видит динамически собранный URL. Внутренний сервис может ходить через общий gateway. Credential scope может скрывать другой tenant. Кэш и очередь могут отложить вызов за пределы выбранного периода. Поэтому removal gate должен хранить границу наблюдения, а не только вердикт.
\nУчебная таблица и код выше не являются telemetry, списком клиентов, результатом incident analysis или production evidence. Их можно использовать как шаблон полей. Реальные значения нужно получать из разрешённых систем и проверять у владельцев этих систем. Если доступ к источнику отсутствует, состояние остаётся unknown.
\nУдаление готово к отдельному change только тогда, когда одновременно выполнены пять условий: ресурс и replacement однозначно определены; notice и его scope опубликованы; каждая известная зависимость имеет состояние и владельца; unknown-зона записана с методом, периодом и stop condition; restore boundary проверена на совместимом контракте. Финальный review должен ответить «да» или «нет» на каждый пункт.
\nПосле удаления проверьте не только код ответа. Проверьте, что новый маршрут принимает прежние обязательные сценарии, что старый маршрут действительно недоступен в заявленной области и что ошибки не появились у клиентов, которых охватил change. Если хотя бы один критерий не проверен, удаление не закончено — оно только запланировано.
\ndeprecated у операции.