D-11 · Обещание совместимости интерфейса#
- Версия: 2.0 · 2026-09-22 · по мастер-шаблону v6.0
- Известно также как: политика совместимости, backward compatibility policy, deprecation policy, «кто у нас потребители», срок жизни поля, contract ownership
- Этап: W2 · Контракты и API · список потребителей уточняется на W9, когда интерфейс уже работает
- Стадия: гринфилд · работающая — на гринфилде знание о потребителях не покупается, а закладывается
- Профиль: дорого изменить · высокое влияние → полная форма с особенностями третьего типа
- Не решает: как устроено версионирование — где ставить номер, что считается ломающим изменением по схеме, как устроен expand/contract: это NET-17; чем описывать контракт и как проверять его в сборке — NET-18; каким стилем отдавать интерфейс — NET-08; где проходит граница, создающая контракт, — D-02
- Не путать с: D-09 — там наше состояние мира и наш откат, здесь чужая система и её право не сломаться; строка «внешние контракты» в состоянии мира
D-09— про этот документ; D-10 — там обещание о доступности, выраженное числом, здесь обещание о неизменности, выраженное сроком - Карточки: NET-17 версионирование API и совместимость · NET-18 контракты и схемы · NET-05 коды состояния и семантика ошибок · NET-20 API против SDK · MSG-06 гарантии доставки и эволюция событий · OPS-12 тестирование, в том числе контрактное · OPS-16 релиз мобильных приложений
Предмет уже, чем название. 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 и три объявленные, но не заполненные оси сравнения — всё исправлено до принятия |