Перейти к содержанию

D-11 · Обещание совместимости интерфейса#

Предмет уже, чем название. NET-17 и NET-18 описывают механику целиком: что совместимо, что нет, куда ставить номер, как жить в два шага, чем проверять в сборке. Пересказывать их здесь незачем.

Здесь решается одно: что мы обещаем потребителю и сколько платим за знание о нём. Обещание совместимости — это срок предупреждения: никогда не сузим · не сузим раньше объявленного срока · без гарантий. Номер версии обещанием не является.

Третий тип решения. Варианты перечисляет модель: два двоичных вопроса о знании и трёхзначный срок дают двенадцать сочетаний, и больше не бывает. Разделы «матрица» и «из чего выбирают» поэтому слиты.

Область решения — один интерфейс, а не система. Внутренний вызов между двумя нашими сервисами и публичное API живут в разных ячейках, и это норма.

Документ собран из записи P-API-01 реестра исследования и мини-исследования М1, которое дало ему главный факт — см. раздел 3.


1. Что выяснить#

Кто на той стороне#

Слово «потребитель» скрывает шесть разных существ, и половину из них при проектировании не вспоминают.

Кто Чем отличается Когда мы узнаём о поломке
Наш код в том же выкате Едет вместе; ломать можно свободно В сборке, до выката
Наш сервис со своим циклом выката Едет на нашем выкате, а чинится на своём Минуты
Партнёр по договору Обновляется по своему плану и через переписку Недели
Мобильное приложение Особый случай, разобранный в NET-17 и OPS-16 Месяцы — и починить уже нечем
Неизвестный потребитель Появился сам: кто-то нашёл эндпоинт и построил на нём работу В инциденте, с его стороны
То, что уже уехало наружу Событие в чужой очереди, выгрузка, доставленный вебхук Никогда: отозвать нельзя

Последняя строка — не экзотика, а ровно та же мысль, что «очереди переживают откат» в D-09. Контракт переживает вызов: доставленное сообщение — это обещание, данное в прошлом и исполняемое в будущем, и совместимость для него бывает только вперёд.

Три координатных вопроса#

Вопрос Где смотреть Координата
Можем ли мы назвать поимённо тех, кто вызывает интерфейс? Ключи доступа, журнал шлюза, договоры. Ответ «да» честен только там, где весь трафик обязан идти через эту точку: иначе список — нижняя граница, а не факт Знание «кто»
Видим ли мы, какие именно поля и методы читаются? Учёт использования полей, журнал ответов, схема запросов у GraphQL. Трафик по версии виден и без этого — из обычного журнала доступа Знание «что»
Какой срок предупреждения мы даём? Продуктовая договорённость; если её нет — то, что фактически делали в последние разы Срок

Четыре проверочных#

Вопрос Зачем
Может ли потребитель сломаться отдельно от нашего выката? Если нет — контракта нет, решение вне пространства
Что входит в обещание помимо схемы: смысл поля, набор кодов ошибок, порядок, сроки? Охват обещания — обязательное поле записи, см. ниже
Кто из потребителей физически не может обновиться в наш срок? Дисквалификатор
Что уже доставлено наружу и не может быть отозвано? Дисквалификатор

Охват обещания — не координата, и это осознанное решение. Ось «что именно гарантируется» существует, но она не меняет ячейку, в которую мы попадаем: она меняет то, к чему ячейка применяется. Сделав её четвёртой координатой, мы получили бы двадцать четыре сочетания вместо двенадцати и ни одного нового вывода. Поэтому охват записывается отдельной строкой ADR, а не координатой.

2. Оси и дисквалификаторы#

Осей ровно три, и они координатные, а не сравнительные. Их нельзя вычеркнуть по правилу «ось, по которой варианты не различаются»: убрав одну, получаем не более простое решение, а неопределённую ячейку. Цены — знания и обещания — не оси сравнения, а содержимое таблицы выхода из ячейки в разделе 3: здесь не выбирают между ценами, здесь платят за переход.

Дисквалификаторы отсекают не ячейки, а сочетания требований — прямое следствие закрытого множества. Отвергнуть ячейку нельзя, не отвергнув того, что в неё привело. Организационные идут первыми — по правилу мастер-шаблона.

  • Предмет обещания определяется не нами и ещё не определён. Схему, коды ошибок и сроки описываем мы; смысл величин задаёт регламент, методика или отраслевой стандарт — и они нередко дописываются параллельно с системой и выходят позже её интерфейса. Обещать неизменность того, чего ещё нет, нельзя: обещание нарушится первым же уточнением, и нарушим его не мы. Отвергается не обещание, а его охват: из него исключается всё, чей смысл задаёт третья сторона, и исключение пишется словами, а не подразумевается. Обещана форма — состав полей, коды, сроки; содержание не обещано. Оговорка, которая при этом обязана прозвучать: исключённое из охвата не защищено ничем, и потребитель должен узнать об этом из той же записи, а не из спора.
  • Срок обещан, а установить его исполнение нечем. Координата знания —, обещание срочное. Объявить можно и не зная адресата — заголовки Deprecation и Sunset уезжают каждому вызывающему, — а вот убедиться, что объявление получено и что им воспользовались, нечем. Отвергается не ячейка, а требование дать срок: либо покупается знание, либо срок записывается тем, чем он является, — вежливой формой «без гарантий».
  • Потребитель не может обновиться в наш срок. Мобильное приложение, встроенное устройство, партнёр с квартальным релизным циклом. Срок короче их цикла — не срок, а формальность, и её цена всплывёт в поддержке. Отвергается длина срока, а не ячейка: либо она растёт до цикла самого медленного потребителя, либо обещание становится бессрочным для него одного.
  • Сужение не проверяется ничем — ни сравнением схем в сборке (NET-18), ни трафиком по сужаемому. Тогда нарушение происходит молча, и это измерено: в релизах Maven образца 2011 года ломающие изменения встречались в минорных практически так же часто, как в мажорных — 35,7 % против 35,8 % (М1). Отвергается требование обещать схему целиком: охват сужается до того, что проверяется.
  • Ломается смысл, а не схема. Поле осталось, значение поменялось; код ошибки тот же, условие другое; порядок перестал быть гарантированным; срок ответа вырос втрое. Сигнатура не изменилась — значит, ни один инструмент этого не поймает, и обещание, сформулированное в терминах схемы, здесь не действует вовсе. Либо охват назван словами, либо обещание уже нарушено.
  • Контракт уже уехал наружу. Доставленный вебхук, выгруженный файл, событие в чужой очереди. Это второй случай «вне пространства»: решение принимается не для вызова, а для формата данных, у которого нет срока предупреждения, потому что предупреждать поздно. Совместимость там только вперёд (MSG-06, D-07).

3. Пространство решений и наша ячейка#

Здесь слиты «матрица» и «из чего выбирают». Двенадцать ячеек: четыре состояния знания × три срока.

Знание: КЧ — известны и кто вызывает, и что читает · К — известны имена, не известно использование · Ч — известно использование без имён · — — не известно ничего. Срок: ∞ — не сузим никогда · T — не сузим раньше, чем через объявленный срок; это нижняя граница предупреждения, а не обязательство снять по его истечении · 0 — без гарантий.

Знание ↓ Срок → ∞ не сузим никогда T не раньше объявленного срока 0 без гарантий
КЧ Платим дважды: знаем всё и всё равно не сужаем. Законно там, где потребитель не обновится ни в какой срок Полевая ячейка. Знание позволяет задать охват как «то, что вы читаете», а не «вся схема»: неиспользуемое снимается вне охвата, а не в обход срока. Используемое — с адресным предупреждением Знание куплено и не применяется. Обычно значит, что его покупали не для этого
К Публичное API с ключами и без учёта полей: предупредить можем, сузить поле безопасно — нет Версионная ячейка. Срок даётся на версию целиком: про отдельные поля ничего не известно Ломаем известных без предупреждения. Единственное достоинство — знаем, кому звонить
Ч Видим, что поле мертво, и всё равно не сужаем. Ячейка, из которой стоит уходить: цена платится зря Объявляем публично, снимаем по нулевому трафику на глубине поля. Дёшево и работает лучше, чем выглядит Ломаем, не предупреждая, и по трафику видим, был ли кто задет. Кто именно — не видим. Измеримо, и потому лучше, чем строкой ниже
— Честная ячейка при незнании. Цена: интерфейс не сужается никогда, и растёт она молча Неотличима от 0. Объявление ушло; получено ли оно и пользуется ли кто-то ещё — установить нечем. Для потребителя это в точности 0, и записывать надо 0 Состояние по умолчанию, в которое интерфейс попадает сам. Законно ровно до дня, когда его кто-то нашёл

Невозможных ячеек здесь нет — есть одна, неотличимая от соседней, и в этом её опасность. —×T выглядит политикой и работает как её отсутствие; именно её отсекает первый дисквалификатор, отвергая не ячейку, а требование дать срок.

Проверка обещания зависит от координаты знания#

Проверка всегда одна — трафик по сужаемому упал до нуля. Различается глубина, на которой её видно.

Что сужаем Чем проверяется Где доступно
Версию целиком Трафик по версии в обычном журнале доступа Во всех ячейках, включая —: считать запросы к /v1 можно, не зная, кто их шлёт
Отдельное поле или метод Учёт использования полей Только Ч и КЧ

Отсюда практическое следствие: без Ч сужать можно только версиями целиком. Это и делает К×T версионной ячейкой, а Ч×T — полевой, и объясняет, почему вторая часто дешевле первой.

Две оговорки к модели#

T — величина, а не флаг. Ячейка не определяет ответ одна: два проекта в К×T со сроком в месяц и со сроком в полтора года получат разные ответы. Ячейка говорит, что мы вправе обещать; длину срока задаёт цикл обновления самого медленного потребителя — второй дисквалификатор.

При КЧ модель применяется к паре «интерфейс × потребитель». Разные сроки для партнёра по договору и для публичного тарифа — не выход из двенадцати ячеек, а двенадцать ячеек, применённые дважды. Знание «кто» в том и состоит, что позволяет обещать адресно.

Уйти из ячейки можно, только купив знание или отпустив обещание#

Хотим уйти Что придётся купить или отпустить
Из — в Ч Учёт использования: какие поля и методы читаются. У GraphQL штатно, у REST — счётчики по полям ответа или разбор журнала. Самая дешёвая покупка в этой таблице
Из — или Ч в К Точку, через которую обязан идти весь трафик: ключи доступа или шлюз (EDGE-03). Это архитектурное изменение, а не настройка
Из ∞ в T Готовность к тому, что часть потребителей объявления не увидит. Без знания это не смягчается ничем, и тогда см. первый дисквалификатор
В 0 из любой Признание, что ломается потребитель и что узнает он об этом сам. Решение законное — но записанное, а не молчаливое

Вне пространства — два случая. Первый: потребитель компилируется и едет тем же выкатом; проверка в одну строку — может ли потребитель сломаться, когда наш выкат прошёл успешно? Второй: контракт уже доставлен наружу, и решается не вызов, а формат данных (пятый дисквалификатор).

Что говорят данные — и чего они не говорят#

Ломающие изменения часты: 30,0 % релизов Maven содержат хотя бы одно. Задетых потребителей мало: 7,9 % клиентов из 293 817 (Ochoa); на другой выборке — 317 библиотек и 260 тыс. клиентов — медиана затронутых 2,54 % при верхней четверти 13,10 %, а из типов, задетых ломающими изменениями, кем-либо используются 7,91 % (Xavier).

Три оговорки, без которых эти числа врут.

Оговорка Что она меняет
Возраст 30,0 % и пара 35,7/35,8 — срез 2011 года. Реплика на данных до 2018-го показывает соблюдение правил заметно лучшим и растущим: 83,4 % обновлений семантическому версионированию соответствуют. Старая цифра — не нынешнее состояние, а то, с чем сравнивать
Популяция Четыре работы из пяти меряют пакетные экосистемы, пятая — девять публичных веб-API. Разбор различий и их направления в М1: перенос завышает безысходность (там список потребителей недостижим по построению, у нас он покупается) и занижает остроту дважды
Распределение Медиана 2,54 % описывает середину, а не всё. У верхней четверти — 13,10 %. «Задевает мало кого» верно для типичного изменения и неверно для каждого четвёртого

Что отсюда следует для решения. Разрыв между числом ломающих изменений и числом задетых потребителей устойчив во всех работах и не зависит от популяции: он следует из того, что интерфейс шире используемой части. Значит, большинство сужений безопасны, и знание Ч делает их наблюдаемо безопасными. Знание К даёт право предупредить; знание Ч даёт возможность увидеть, что предупреждать некого. Второе дешевле и закрывает больше случаев, а покупают обычно первое.

Чего данные не дали: ни одной цифры про внутренние сервисные контракты, ни одной про цену политики, и ничего про то, как часто потребитель оказывается неизвестным (М1, раздел 5). Утверждения этого документа о том, какая ячейка встречается чаще, — суждение, а не измерение, и здесь их поэтому нет.

4. Эвристики#

Обещание — это срок предупреждения, а не номер версии. /v1 в пути не обещает ничего: в Maven образца 2011 года в минорных релизах ломали почти столько же, сколько в мажорных, а веб-API, по разбору девяти публичных API, нередко ломают совместимость, не меняя идентификатор.

Обещание, исполнение которого нечем установить, обещанием не является. Тот же дисквалификатор, что в D-06, D-09 и D-10: путь деградации, который не исполняется; откат, который не пробовали; измеритель, не показывающий число сегодня. Здесь — срок, объявленный тем, кого мы не видим.

Сужать можно на той глубине, на какой видишь. NET-17 говорит, чем снятие управляется — нулевым трафиком, а не календарём. Документ добавляет к этому одно: на какой глубине это правило исполнимо. Без Ч нулевой трафик виден только по версии целиком, значит и сужать без Ч можно только версиями.

Знание покупает не право сузить, а свидетельство, что сужение никого не задело. Сузить можно и не зная — это ячейка 0, и она законна. Поэтому выбор здесь не между «можно» и «нельзя», а между тремя способами заплатить: купить знание, расширить обещание или отдать счёт потребителю.

Ничья между соседними ячейками решается тем, кто платит. Цена нашей осторожности ложится на нас и видна; цена нашей поспешности ложится на потребителя и не видна. При равенстве прочего берётся более широкое обещание. Но ничья возникает там, где знания нет: при Ч её обычно не бывает вовсе, потому что за нас отвечает трафик.

Одна ячейка на все интерфейсы — признак того, что её не выбирали. Внутренний вызов между двумя сервисами одной команды и публичное API не обязаны жить в одной ячейке и обычно не живут.

«Нет данных» здесь не бывает: незнание — это координата. Пустая ячейка в других решениях означает «не померили». Здесь отсутствие знания о потребителях и есть значение —, то есть решение, принятое молчанием. Поэтому «мы не знаем, кто нас вызывает» не откладывает решение, а определяет его.

5. Запись в ADR#

Поле ADR Чем заполняется
Контекст Кто на той стороне — по строкам таблицы раздела 1; что уже уехало наружу
Дисквалификаторы Какие требования отвергнуты и что превратилось в работу до объявления политики
Рассмотренные варианты Снимком копируются ответы на три координатных вопроса — они и есть условия момента. Перечень ячеек от рынка не зависит и не копируется
Решение Ячейка (знание × срок), длина срока и охват обещания: что гарантируется помимо схемы
Обоснование Почему эта ячейка: что дешевле — купить знание, расширить обещание или отдать счёт потребителю
Последствия Что придётся построить (учёт полей, реестр, проверка в сборке); на какой глубине обещание проверяется
Условия пересмотра Раздел 6
Метод D-11 v<версия> · шаблон ADR v<версия> — рыночного снимка нет

Три строки обязательны: ячейка, длина срока и охват обещания словами. Запись, в которой обещание выражено как «у нас v2», обещания не содержит.

Если часть предмета определяет третья сторона — исключение называется прямо, отдельной строкой охвата: что именно не обещано и кто это определит. Умолчание здесь читается потребителем как обещание (первый дисквалификатор).

Связанное решение. Изменение контракта почти всегда едет вместе с выкатом. Общее у них не окно — окно отката измеряется часами, окно устаревания месяцами, — а параметр: момент, с которого новый контракт виден снаружи. После него откат ломает уже не нас, а потребителя, успевшего им воспользоваться; до него откат свободен. Записываются они одним ADR (мастер-шаблон, раздел о связанных решениях). Второй случай связанности — граница сервиса: разрез создаёт новый контракт, и обещание по нему приходится давать в тот же день, когда принимается граница.

6. Условия пересмотра#

  • Третья сторона определила то, что было исключено из охвата. Исключение можно снять, а обещание расширить — и это единственный случай, когда охват растёт, а не сужается.
  • Появился потребитель, которого нет в списке. Координата «кто» изменилась молча, и ячейка стала другой без нашего участия.
  • Интерфейс стал доступен за пределами периметра — то же самое, но заранее.
  • Трафик по устаревшей версии или полю перестал падать: срок кончится, а снимать всё равно будет нельзя.
  • Срок объявлен, прошёл, и снятия не было ни разу. Обещание стало бессрочным по факту — и в следующий раз ему не поверят.
  • Смысл поля или набор кодов ошибок изменился, и проверка этого не поймала.
  • Появилась доставка наружу, которую нельзя отозвать: вебхук, выгрузка, публикация события.
  • Куплено знание, которого не было, — ячейка сменилась, и обещание можно пересобрать дешевле.

Проверка перед тем, как считать решение принятым — пять пунктов в шаблоне. Шестой, местный: названо, что именно обещано, и это не номер версии. Проверяется одним вопросом к записи: сможет ли потребитель, прочитав её, сказать, до какого дня он вправе не трогать свой код.

Источники: М1 · мини-исследование по P-API-01 — пять работ по эволюции API, оговорки о возрасте и популяции там же · Raemaekers, van Deursen, Visser, SCAM 2014 · Ochoa, Degueule, Falleri, Vinju, EMSE 2022 · Xavier, Brito, Hora, Valente, SANER 2017 · Venturini и др., TOSEM 2023 · Sohan, Anslow, Maurer, SERVICES 2015 · RFC 9745 — The Deprecation HTTP Header Field · 00-workflow.md, шаг W2

История версий#

Версия Дата Изменение
2.0 2026-09-22 MAJOR, по итогам прогона U-02, развилка Р1. Добавлен шестой дисквалификатор — предмет обещания определяется не нами и ещё не определён, — и он поставлен первым как организационный. Прежние пять описывали, чего стоит обещание; ни один не описывал случай, когда обещать нечего, потому что смысл обещаемого задаст третья сторона позже. Обязательных строк записи по-прежнему три, но у охвата появилось требование: исключение, сделанное из-за третьей стороны, называется прямо. Старым ADR: он один — ADR-0001 прогона U-02, и он уже применил это правило вручную: из охвата исключён смысл величин, который определит декабрьская методика. Переделывать его не нужно; перепроверить стоит обратное — что исключение названо словами, а не подразумевается. Именно это применение вручную и показало, что правила в документе нет
1.0 2026-09-18 Первая версия. Собрана из записи P-API-01 реестра и мини-исследования М1. Предмет сужен карточным правилом: механика версионирования остаётся в NET-17 и NET-18, здесь решается обещание и цена знания о потребителях. Второй документ третьего типа после D-04: 3 координатных оси, 12 ячеек, 4 проверочных вопроса, 5 дисквалификаторов. Обещание определено как нижняя граница предупреждения — это и делает его записываемым. До первой записи документ прошёл независимую рецензию; она нашла пересказ карточки в эвристике, пропавшую оговорку о возрасте цифр, две противоречивые трактовки координаты T и три объявленные, но не заполненные оси сравнения — всё исправлено до принятия