8 lines
22 KiB
JSON
8 lines
22 KiB
JSON
{
|
||
"index": 114,
|
||
"slug": "editorial-2024-11-practice-deprecation",
|
||
"title": "Депрекация API без аварии: карта потребителей и gate удаления",
|
||
"excerpt": "Дата Sunset и пустой график вызовов не доказывают, что endpoint можно удалить. Показываю, как ограничить scope, собрать карту потребителей и остановить опасное удаление.",
|
||
"contentHtml": "<p>В задаче на удаление API обычно есть убедительная дата: после 3 февраля старый путь должен исчезнуть. В день релиза график вызовов пуст, в OpenAPI стоит <code>deprecated: true</code>, а владельца старого клиента никто не знает. Обработчик удаляют. Через час внешний интегратор получает 404, а команда не может ответить, кто предупредил потребителя и где описан переход.</p>\n<p>Причина не в том, что календарь составили плохо. Дата задаёт план, но не доказывает отсутствие потребителей. Пустой график описывает только выбранный маршрут, период, прокси, credentials и качество телеметрии. Пометка в схеме сообщает о жизненном цикле операции, но не обновляет SDK и не доставляет уведомление. Без карты зависимостей удаление превращается в догадку.</p>\n<p><strong>Рабочее правило:</strong> сначала докажите границы проверки, затем принимайте решение об удалении одной операции. Результат проверки может быть <code>observed</code>, <code>not observed in scope</code> или <code>unknown</code>. Последние два результата нельзя склеивать с фразой «никто не использует». При активном или неизвестном потребителе автоматический removal gate закрыт.</p>\n<h2>Что именно мы собираемся удалить</h2>\n<p>Фраза «убираем v1» слишком широка для безопасного изменения. Она может означать один метод, несколько ресурсов, SDK-функцию, webhook или всю версию API. Начните с одной операции и запишите её как контракт: HTTP-метод, URI-шаблон, <code>operationId</code>, форму запроса, ответы, коды ошибок и границу авторизации.</p>\n<p>Такой scope нужен не для бюрократии. Владелец сервиса может подтвердить удаление <code>POST /v1/posting</code>, но не всего <code>/v1</code>. Новый URL может принимать тот же JSON, но иначе обрабатывать повторную доставку, идемпотентность, пагинацию или права. Поэтому рядом с replacement фиксируйте семантические отличия. Совпадение URL и формата ещё не означает совместимость.</p>\n<table><caption>Минимальный контракт removal proposal</caption><thead><tr><th>Поле</th><th>Что записать</th><th>Почему это ограничивает риск</th></tr></thead><tbody><tr><td>Operation</td><td><code>POST /v1/posting</code> и <code>operationId</code></td><td>Не даёт распространить решение на соседние методы</td></tr><tr><td>Replacement</td><td>Новый метод, схема, ошибки и правила повторов</td><td>Показывает, что переход — не простая замена строки</td></tr><tr><td>Population</td><td>Известные, внешние и неизвестные классы клиентов</td><td>Не маскирует неполноту инвентаризации</td></tr><tr><td>Evidence boundary</td><td>Инструмент, окно, маршрут, credentials, sampling и пропуски</td><td>Отделяет наблюдение от доказательства полноты</td></tr><tr><td>Restore boundary</td><td>Что возвращается и кто проверяет восстановление</td><td>Превращает rollback из обещания в проверяемое условие</td></tr></tbody></table>\n<h2>Почему одного сигнала недостаточно</h2>\n<p>Карта потребителей должна разделять классы сведений. Поиск исходников показывает вызовы в просмотренной области. Manifest показывает объявленную зависимость. Access log показывает запросы, дошедшие до конкретного слоя. Сведения от владельца подтверждают намерение команды, но не заменяют runtime-проверку. У каждого сигнала свой blind spot, поэтому итоговая строка хранит и результат, и границу.</p>\n<table><caption>Как читать отрицательный результат</caption><thead><tr><th>Результат</th><th>Что он действительно означает</th><th>Чего он не доказывает</th><th>Следующее действие</th></tr></thead><tbody><tr><td><code>source: found</code></td><td>В просмотренном репозитории найден вызов</td><td>Что это единственный потребитель</td><td>Назначить владельца и запланировать миграцию</td></tr><tr><td><code>traffic: zero</code></td><td>За окном не было видимых запросов</td><td>Что нет batch, другого gateway или редкого клиента</td><td>Расширить окно и сверить маршрут и credentials</td></tr><tr><td><code>declared: absent</code></td><td>Зависимость не записана в проверенном manifest</td><td>Что внешний binary или сгенерированный SDK отсутствует</td><td>Проверить inventory и канал объявления</td></tr><tr><td><code>scope: unknown</code></td><td>Полнота области не доказана</td><td>Что endpoint безопасно удалять</td><td>Сохранить совместимость или принять риск отдельно</td></tr></tbody></table>\n<figure><img src='/assets/editorial/2024/deprecation-2024-consumer-map.svg' alt='Схема удаления API: операция связана с картой потребителей, владельцами, replacement и границами доказательств; active или unknown consumer ведёт к остановке удаления'><figcaption>Карта показывает порядок решения: сначала scope и evidence, затем миграция; неизвестная область закрывает автоматическое удаление. Имена на схеме условные.</figcaption></figure>\n<p>Например, отсутствие записи в репозитории не видит закрытый исходный код, сгенерированный клиент или старый бинарный клиент. Нулевой access log не видит запрос, который прошёл через другой hostname, не попал в выбранный gateway или пришёл раз в квартал. Даже полный на вид отчёт относится к своему окну и credential class. Именно поэтому строка <code>unknown consumer</code> полезнее пустой ячейки: она сохраняет незамкнутую область в решении.</p>\n<h2>Депрекация не равна отключению</h2>\n<p>В OpenAPI 3.1 поле <code>deprecated</code> у Operation Object объявляет операцию устаревшей и рекомендует потребителям прекратить её использование. Это описание контракта. Оно не ищет клиентов, не выпускает новую версию SDK и не меняет ответ сервера.</p>\n<p>Runtime-уведомление решает другую задачу. Заголовок <code>Deprecation</code> из RFC 9745 сообщает клиенту дату депрекации; его значение — structured date, например <code>Deprecation: @1688169599</code>. Тот же RFC подчёркивает: сам факт депрекации не меняет поведение ресурса. Для документации можно добавить <code>Link</code> с отношением <code>deprecation</code>, где указаны replacement и миграционная инструкция.</p>\n<p><code>Sunset</code> из RFC 8594 сообщает, что ресурс ожидается недоступным после указанного HTTP-времени. Это подсказка для клиента, а не гарантия того, что до даты всё будет работать, а после неё обязательно появится конкретный код. В RFC 9745 также зафиксировано, что Sunset не должен быть раньше даты Deprecation. Проверяйте эти заголовки на фактическом ответе и не выдавайте их за доказательство миграции.</p>\n<pre><code>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</code></pre>\n<p>Команда предназначена для собственного стенда: замените <code>api.example.test</code> адресом среды, где разрешена такая проверка, и не помещайте токены в shell history или публикацию. Если endpoint требует авторизацию, задайте её способом, принятым в проекте, и отдельно запишите, какой класс credentials был покрыт. Ответ одного proxy не подтверждает поведение другого слоя.</p>\n<h2>Как собрать карту потребителей</h2>\n<p>Минимальная запись содержит consumer, класс evidence, owner, replacement, migration path, срок, канал уведомления и evidence boundary. Для известного клиента нужна не только строка «migrated», а ссылка на проверку: версия SDK, тест совместимости, успешный запрос или подтверждённый rollout. Статус в таблице — это утверждение, его источник и область должны быть видны рядом.</p>\n<pre><code>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</code></pre>\n<p>Это CSV-образец, а не список реальных клиентов. В рабочем процессе его можно хранить в системе, где есть история изменений и доступ владельцев. Не записывайте секреты, токены и персональные данные: для связи достаточно идентификатора системы, команды и ссылки на разрешённое доказательство.</p>\n<p>Отдельно проверяйте четыре направления: код и сгенерированные клиенты; реестр SDK и зависимостей; сетевые логи на ingress и gateway; коммуникацию с внешними владельцами. Дублируйте результат только там, где сигналы независимы. Три отчёта из одного прокси не превращаются в три доказательства.</p>\n<h2>Removal gate должен уметь остановить изменение</h2>\n<p>Хороший gate формулируется отрицательными условиями. Удаление запрещено, если найден активный consumer; если неизвестная область не ограничена и остаточный риск не принят; если replacement меняет значимую семантику без плана; если notice не связан с affected scope; если не описано восстановление. Ранняя дата не отменяет ни одного из этих условий.</p>\n<p>Разделяйте два решения. Первое — можно ли объявить операцию устаревшей и начать миграцию. Второе — можно ли менять runtime так, чтобы старый путь перестал отвечать. Между ними должны быть окно совместимости, подтверждённые владельцы и отдельное human review. Не объединяйте удаление route, изменение прав, чистку документации и удаление SDK в один непроверяемый коммит.</p>\n<table><caption>Вердикты перед удалением</caption><thead><tr><th>Вердикт</th><th>Условие</th><th>Разрешённое действие</th></tr></thead><tbody><tr><td><code>block: active</code></td><td>Есть запросы или подтверждённая зависимость</td><td>Остановить removal, назначить миграцию</td></tr><tr><td><code>block: unknown</code></td><td>Область проверки не замкнута</td><td>Расширить наблюдение или продлить совместимость</td></tr><tr><td><code>allow: review</code></td><td>Scope, replacement, owners, notice и restore boundary описаны</td><td>Открыть отдельный review; не удалять автоматически</td></tr><tr><td><code>allow: change</code></td><td>Политика проекта допускает изменение после review</td><td>Применить узкий change и проверить соседние операции</td></tr></tbody></table>\n<h2>Воспроизводимая последовательность</h2>\n<ol><li><strong>Зафиксируйте scope.</strong> Назовите одну operation, метод, URI, operationId, схемы ответа и authentication boundary.</li><li><strong>Опишите replacement.</strong> Сравните семантику, ошибки, идемпотентность, повторы, лимиты и права, а не только URL.</li><li><strong>Соберите карту.</strong> Проверьте source usage, manifests, SDK inventory, gateway и ingress. Добавьте отдельную строку unknown, если полнота не доказана.</li><li><strong>Разметьте evidence.</strong> Для каждого результата сохраните инструмент, окно, маршрут, credentials, sampling и blind spots.</li><li><strong>Назначьте владельцев.</strong> У каждой известной зависимости должны быть owner, migration path, версия replacement и способ подтвердить переход.</li><li><strong>Опубликуйте notice.</strong> Свяжите OpenAPI, Deprecation, Sunset, Link-документацию и миграционный план с тем же scope.</li><li><strong>Проверьте stop conditions.</strong> Active, unknown без принятого риска, несовместимый replacement и отсутствующий restore boundary закрывают gate.</li><li><strong>Откройте отдельный review.</strong> В решении укажите остаточный риск, затронутую operation и критерии отказа. Только после допуска создавайте runtime change.</li><li><strong>Сверьте результат.</strong> Повторите проверку после изменения: старый путь изменился ожидаемо, replacement доступен, а соседние методы и правила авторизации не затронуты.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта схема не делает неизвестных клиентов видимыми автоматически. Она не заменяет договор с внешним партнёром, анализ закрытого кода, юридические сроки уведомления, требования к доступности или политику change management. Конкретное окно наблюдения и достаточная полнота зависят от трафика: для ежедневного запроса 30 дней может быть полезным сигналом, для квартального batch — нет.</p>\n<p>Заголовки тоже имеют границы. Клиент может их не читать, промежуточный кеш может изменить наблюдаемую картину, а <code>Deprecation</code> и <code>Sunset</code> не доказывают, что владелец клиента получил notice. RFC 8594 не определяет, будет ли после Sunset 4xx, 3xx или другой отказ. Поэтому не стройте единственную защиту на runtime-заголовке и не обещайте точный код ответа, если он не закреплён вашим контрактом.</p>\n<p>Если потребитель активен, безопасное действие — остановить удаление и дать ему совместимый путь. Если потребитель неизвестен, безопасное действие — расширить scope или сохранить endpoint. Residual risk можно принять только тем владельцем и в том процессе, которые отвечают за последствия; запись «вроде никто не использует» таким решением не является.</p>\n<h2>Критерий готовности</h2>\n<p>Операция готова к removal review, когда её scope однозначен, replacement проверен по семантике, известные потребители имеют владельцев и миграционные доказательства, неизвестная область либо закрыта, либо явно принята, а notice и restore boundary доступны. Решение должно быть воспроизводимым: другой инженер может повторить запрос, понять границу логов, найти источник строки карты и назвать условие остановки.</p>\n<p>Итогом не обязательно будет удаление. Иногда лучший результат — продлить совместимость, добавить телеметрию или сузить контракт. Дата в календаре становится полезной только после того, как рядом появились scope, доказательство, владелец и понятный следующий шаг.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://spec.openapis.org/oas/v3.1.0.html#operation-object\" target=\"_blank\" rel=\"noopener\">OpenAPI Specification 3.1.0: Operation Object</a> — поле <code>deprecated</code> объявляет операцию устаревшей и не описывает список потребителей.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9745.html\" target=\"_blank\" rel=\"noopener\">RFC 9745: The Deprecation HTTP Response Header Field</a> — формат заголовка, отношение <code>deprecation</code>, связь с документацией и границы runtime-сигнала. RFC опубликован в 2025 году, поэтому для процесса, зафиксированного раньше, отдельно проверьте поддерживаемый набор заголовков.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8594.html\" target=\"_blank\" rel=\"noopener\">RFC 8594: The Sunset HTTP Header Field</a> — HTTP-date, ожидаемая недоступность ресурса и ограничение Sunset как подсказки, а не гарантии конкретного поведения.</li></ul>"
|
||
}
|