$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогСемантическое версионирование (SemVer) для API: как не сломать пользователей

Семантическое версионирование (SemVer) для API: как не сломать пользователей

$ sudo teach IT
·24 июля 2026 г.·10 мин·51
Семантическое версионирование (SemVer) для API: как не сломать пользователей

Разберём правила версионирования для публичных эндпоинтов и схем данных, а также типовые причины несовместимостей. Поймём, когда нужен minor, а когда обязательно major.

Содержание
Что именно “ломает” пользователей: интуитивная модель совместимостиПрямая (backward compatibility) — клиент без изменений продолжает работатьПоведенческая несовместимостьКонтрактная несовместимость схемНесовместимость транспортного уровняВведение в SemVer: как интерпретировать MAJOR/MINOR/PATCH для APIПрактическое правилоЧто считать публичным контрактом в APIЭлементы запросаЭлементы ответаСемантика и инвариантыPATCH: что можно менять безопасно1) Устранение багов без изменения контракта2) Улучшения наблюдаемости без влияния на контракт3) Критические security fixes при сохранении APIMINOR: добавления, которые остаются совместимымиДобавление новых опциональных полей в ответДобавление новых опциональных query-параметровДобавление новых эндпоинтовДобавление новых значений в enumПоддержка новых content-types при сохранении старыхMAJOR: изменения, требующие слома контракта (или гарантий)1) Переименование или удаление полей2) Изменение типов и форматов3) Изменение обязательности (required/optional)4) Изменение semantics: то, что “значит поле”5) Изменение кодов ответов и договорённостей об ошибках6) Пересмотр правил валидации с “отказом” вместо “допуска”7) Поведенческие контракты на уровне идемпотентности и ретраевКритерий “minor vs major”: чеклист для принятия решенияШаг 1: можно ли представить “старый клиент” как программу, следующую спецификации?Шаг 2: изменения расширяют контракт или сужают?Шаг 3: есть ли эффект “по умолчанию”?Шаг 4: есть ли “скрытая” зависимость клиентов от статусов/ошибок?Шаг 5: что с документацией и версионностью схем?Несовместимости на уровне схем: почему JSON и “просто добавили поле” иногда не спасаютСтрогая типизация и десериализацияИзменение nullabilityИзменение ограничений (constraints)Пример: как сформулировать правила на конкретных артефактах OpenAPIКритическое: совместимость схемПример политики в терминах “API breaking change”Автоматизация: как снизить риск неверного MAJORИдеяМинимальный пример сравнения схем (идея)

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

Семантическое версионирование (SemVer) — попытка дать формальный язык изменениям и контрактам. Для API это не просто про цифры MAJOR.MINOR.PATCH. Это способ честно описывать обратную совместимость и прогнозировать влияние релизов. В этой статье разберём, как применять SemVer к публичным эндпоинтам и схемам данных, какие причины несовместимостей встречаются чаще всего, и как понять: когда можно ограничиться minor, а когда без major не обойтись.


Что именно “ломает” пользователей: интуитивная модель совместимости

Прежде чем говорить о правилах, важно договориться о терминах. В контексте API «совместимость» обычно понимают как способность клиентского кода продолжать работать без изменений после обновления сервера и схем.

Удобно разделить совместимость на несколько уровней:

Прямая (backward compatibility) — клиент без изменений продолжает работать

Это то, что нам нужно от minor и patch. Если клиент отправляет старые запросы и ожидает старые ответы, сервер не должен нарушать эти ожидания.

Поведенческая несовместимость

Даже если формат ответа “не поменялся” (например, JSON остался JSON), изменение семантики может сломать логику: поменялся смысл поля, порядок сортировки по умолчанию, правила ошибок, гарантии идемпотентности.

Контрактная несовместимость схем

Это самая очевидная группа: поля исчезли, типы изменились, обязательность поменялась, поля получили новый формат или значения перестали подчиняться прежним ограничениям.

Несовместимость транспортного уровня

Случается реже, но тоже важно: смена статусов, заголовков, схемы аутентификации (например, OAuth scopes), требований к CORS, ограничения на методы/редиректы и т. п.

SemVer в API должен отражать именно эти риски.


Введение в SemVer: как интерпретировать MAJOR/MINOR/PATCH для API

SemVer формально описывает правила версионирования пакетов:

  • PATCH (x.y.Z) — обратимые исправления, не меняющие публичный контракт.
  • MINOR (x.Y.z) — добавления функциональности в обратной совместимости.
  • MAJOR (X.y.z) — изменения, нарушающие обратную совместимость.

Для API эти правила нужно адаптировать к “публичному контракту”: эндпоинты, параметры, ответы, схемы, коды ошибок, договорённости о семантике.

Практическое правило

  • Если клиент, написанный под прежнюю версию, может продолжать работать без правок, то это кандидат на minor или patch.
  • Если гарантировать это нельзя (или для большинства клиентов нужно менять код), это кандидат на major.

Однако “может продолжать работать” — не бинарный факт. Нужны методичные критерии.


Что считать публичным контрактом в API

В API контракт — это не только GET /items/{id} и JSON. В реальных интеграциях публичными становятся:

Элементы запроса

  • URI и HTTP-метод (GET/POST/PUT/PATCH/DELETE).
  • Схема и формат query-параметров.
  • Тело запроса (формат, структура, обязательные/опциональные поля).
  • Заголовки (например, Idempotency-Key, If-Match, Authorization, кастомные заголовки корреляции).
  • Кодировки и правила сериализации (например, как обрабатываются пробелы/escape, формат даты).

Элементы ответа

  • HTTP status codes и правила их назначения.
  • JSON-схемы: поля, типы, nullability, перечисления (enum), формат чисел (decimal/float), ограничения.
  • Текст ошибок (если клиенты парсят message).
  • Заголовки (paging, rate limit и т. п.).

Семантика и инварианты

  • Идемпотентность для метода/эндпоинта.
  • Гарантии порядка вывода, стабильность сортировки по умолчанию.
  • Правила валидации (например, что считается допустимым значением).
  • Политика отмены/обработки long-running операций.
  • Поведение при частичном успехе (если есть массив ошибок).

Чем больше вы позволяете клиентам полагаться на поведение, тем строже нужно относиться к версиям.


PATCH: что можно менять безопасно

Обычно patch — это исправления, которые не меняют форму и смысл контракта. В API это часто:

1) Устранение багов без изменения контракта

  • Ошибка в расчёте поля, которое по контракту всегда было number, а возвращалось “почти number”.
  • Неправильное сопоставление фильтров, которое давало неверный результат, но формат ответа оставался корректным.
  • Исправление неверного status code в ситуации, где ожидалось другое, но спецификация была понятна.

2) Улучшения наблюдаемости без влияния на контракт

  • Логирование, tracing, добавление новых внутренних event’ов.
  • Добавление заголовков, если клиенты их игнорируют и заголовки не конфликтуют с существующими (хотя “потенциально” — тонкое место).

3) Критические security fixes при сохранении API

Если вы ужесточаете безопасность так, что часть клиентов начинает получать 401/403 вместо 200/422 — это может стать несовместимостью. Поэтому безопасность почти всегда заканчивается major, если старые клиенты не готовы.

Ключевой тест для PATCH: клиент, следующий спецификации, должен продолжить получать ответы той же формы и с той же семантикой.


MINOR: добавления, которые остаются совместимыми

minor — это добавление возможностей, не ломающее старые клиенты. Для API это означает: вы расширяете контракт так, чтобы старый клиент не заметил изменения или продолжал работать.

Добавление новых опциональных полей в ответ

Пример: было

code
{ "id": "123", "name": "A" }

стало

code
{ "id": "123", "name": "A", "metadata": { "source": "crm" } }

Если metadata опционально и отсутствует — старый клиент не сломается. Даже если присутствует — если клиент использует “пропускаем неизвестные поля” (что типично для JSON-парсеров), совместимость сохраняется.

Подводный камень: если старые клиенты используют строгие схемы (например, десериализация в строго типизированную модель с запретом “unknown fields”), то любое новое поле может ломать. В таких экосистемах лучше заранее документировать поведение сериализации и политику unknown fields — или решать это через major. SemVer формально предполагает разумное поведение клиентов, но на практике надо учитывать реальные фреймворки.

Добавление новых опциональных query-параметров

Если новые параметры не меняют поведение по умолчанию, это хороший minor.

  • Старые запросы работают как прежде.
  • Новые клиенты используют новые параметры для улучшенного поведения.

Добавление новых эндпоинтов

Добавление нового endpoint’а обычно не ломает существующие. Это кандидат на minor.

Добавление новых значений в enum

Осторожно: добавление нового значения в перечисление считается совместимым в семантике “клиент не должен падать при неизвестном enum”. Если клиенты делают “switch без default” и падают на неожиданных значениях — это уже их хрупкость. Но вы должны понимать риск.

Подводный камень: если enum использовался как “закрытый” список в логике клиента, новое значение может привести к некорректному поведению. Формально контракт расширен, но практически — возможен “тихий” сбой.

Поддержка новых content-types при сохранении старых

Например, дополнительно поддержали application/json; charset=utf-8 (часто по факту это то же самое) или добавили альтернативный формат представления, сохранив JSON.


MAJOR: изменения, требующие слома контракта (или гарантий)

major нужен, когда вы меняете публичный контракт так, что старые клиенты могут перестать работать или начнут получать неверное поведение.

1) Переименование или удаление полей

  • Удаление поля из ответа.
  • Замена snake_case на camelCase без поддержки старого формата.
  • Переименование параметра query или тела запроса.

Даже если “можно легко поправить” — это всё равно несовместимость.

2) Изменение типов и форматов

Классический пример: поле было integer, стало string. Или дата была в ISO 8601, а стала Unix timestamp.

Проблема особенно остра, если клиентские схемы строгие.

3) Изменение обязательности (required/optional)

Если поле стало обязательным — старые клиенты не отправят его и получат ошибку валидации.

Если поле раньше было optional и возвращалось не всегда, а теперь всегда возвращается — обычно это minor (старые клиенты игнорируют доп. поля), но если для возврата ввели новые значения/гарантии — иногда это может стать minor/patch в зависимости от семантики. Однако если вы ужесточаете правила так, что клиент начинает получать другое поведение — риск major.

4) Изменение semantics: то, что “значит поле”

Например:

  • Поле status раньше означало жизненный цикл A, стало означать жизненный цикл B.
  • limit и offset поменяли интерпретацию.
  • Порядок сортировки по умолчанию изменился.

Это может быть самым коварным источником поломок: формат не меняется, но бизнес-логика клиента рушится.

5) Изменение кодов ответов и договорённостей об ошибках

Если вы меняете статус кода:

  • было 200 + ошибка внутри JSON,
  • стало 400 / 422, клиенты, которые обрабатывают ошибки “по старому паттерну”, могут ломаться.

Если вы меняете формат тела ошибки или структуру errors[], это тоже потенциальный major.

6) Пересмотр правил валидации с “отказом” вместо “допуска”

Если раньше поле принимали как string произвольного вида, а теперь требуете конкретный regex и начинаете отвечать ошибкой — старые клиенты могут перестать проходить валидацию.

Если валидация “расширилась” (стало принимать больше значений) — это minor. Но “ужесточение” почти всегда ведёт к major.

7) Поведенческие контракты на уровне идемпотентности и ретраев

Если endpoint ранее поддерживал идемпотентность при повторной отправке POST с тем же Idempotency-Key, а теперь гарантии пропали — это может быть серьёзной несовместимостью. Для распределённых систем это ломает обработку ретраев.


Критерий “minor vs major”: чеклист для принятия решения

Ниже — практический чеклист, который помогает избежать “споров после релиза”.

Шаг 1: можно ли представить “старый клиент” как программу, следующую спецификации?

Если да — проверяйте:

  • Старые запросы принимаются?
  • Старые ответы имеют тот же формат?
  • Ошибки (статусы и структура) совместимы?
  • Семантика ключевых полей та же?

Если любой пункт нет — это, скорее всего, major.

Шаг 2: изменения расширяют контракт или сужают?

  • Расширяют (добавили опциональные поля/параметры, разрешили больше значений) → minor.
  • Сужают (удалили, переименовали, сделали обязательным, ужесточили валидацию, сменили формат) → major.

Шаг 3: есть ли эффект “по умолчанию”?

Даже если вы добавили поле/параметр, но он влияет на поведение по умолчанию — это может стать несовместимостью. Если “присутствие новых параметров” меняет поведение старых запросов (например, по умолчанию включили новый флаг) — это major или минимум очень осмысленный minor с миграцией.

Шаг 4: есть ли “скрытая” зависимость клиентов от статусов/ошибок?

Если вы меняете ошибки “на практике” (например, раньше отдавали 200 и ошибку в body, теперь 422) — это ломает клиентов, которые парсят конкретные форматы.

Шаг 5: что с документацией и версионностью схем?

Если вы используете OpenAPI/JSON Schema, то изменения должны соответствовать версионности спецификации. Полезно иметь автоматизированные проверки (см. ниже) — они снижают человеческий фактор.


Несовместимости на уровне схем: почему JSON и “просто добавили поле” иногда не спасают

JSON “прощает” многое, но не всё.

Строгая типизация и десериализация

Многие клиенты используют строгие модели:

  • Go: json.Decoder.DisallowUnknownFields()
  • Java/Kotlin: Jackson с FAIL_ON_UNKNOWN_PROPERTIES
  • C#: System.Text.Json может игнорировать unknown fields по умолчанию, но многие проектируют модели и валидацию иначе

Если клиент не готов к неизвестным полям, добавление даже опционального поля может быть несовместимым.

Практический вывод: чтобы SemVer работал, вы должны описывать политику “unknown fields” в вашем контракте и в примерах. Если неизвестные поля допускаются — фиксируйте это.

Изменение nullability

  • Было: поле может отсутствовать или быть null.
  • Стало: поле всегда строка и никогда null.

С точки зрения клиентов это может быть ломающее изменение. И особенно опасно, если клиенты отличают “отсутствует” и “null”.

Изменение ограничений (constraints)

Даже если тип не поменялся (string осталось строкой), ограничение может стать несовместимостью:

  • минимальная длина стала больше,
  • enum расширился/сузился,
  • формат (regex) ужесточился.

Если такое изменение влияет на набор допустимых значений в запросах — это обычно major.


Пример: как сформулировать правила на конкретных артефактах OpenAPI

Допустим, у вас есть публичная спецификация OpenAPI. Тогда SemVer можно связать с изменениями конкретных компонентов: Path/Operation, Schema, Parameter.

Критическое: совместимость схем

Если вы генерируете типы для клиентов, то Schema changes должны интерпретироваться строго:

  • Добавление optional поля в response schema → minor
  • Добавление required поля → major
  • Удаление поля → major
  • Изменение type (string → integer) → major
  • Изменение format (date-time → int64) → major
  • Изменение enum: удалили значения → major, добавили новые — обычно minor, но с оговоркой о поведении клиентов

Пример политики в терминах “API breaking change”

В документации к релизу полезно описывать не только число версии, но и список категорий изменений: “breaking / non-breaking / patch”.


Автоматизация: как снизить риск неверного MAJOR

Ручная оценка “минор или мажор” часто превращается в угадывание по ощущениям. Практика показывает: лучше иметь автоматизированные проверки контрактных изменений.

Идея

  1. Храните спецификацию API версионно (OpenAPI/JSON Schema).
  2. На PR сравнивайте “старую” и “новую” спецификацию.
  3. Классифицируйте diff по правилам совместимости.

Существуют готовые подходы и инструменты для “contract testing” и “API compatibility checks”, но даже без конкретного инструмента вы можете организовать проверку:

  • сравнивайте удаление/изменение required полей,
  • отслеживайте типы полей,
  • проверяйте status codes и схемы ошибок,
  • смотрите изменения параметров.

Минимальный пример сравнения схем (идея)

Если у вас JSON Schema, можно хотя бы программно выявлять удаление required-полей и изменения типов. Например, грубая проверка на уровне required и types:

code
import json

def load_schema(path):
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)

def index_properties(schema):
    # Упрощение: предполагаем, что schema вида {"type":"object","properties":{...},"required":[...]}
    props = schema.get("properties", {})
    required = set(schema.get("required", []))
    types = {name: prop.get("type") for name, prop in props.items()}
    return props, required, types

def breaking_required_removal(old_schema, new_schema):
    _, old_required, _ = index_properties(old_schema)
    _, new_required, _ = index_properties(new_schema)
    removed_required = old_required - new_required
    return removed_required  # формально removal required само по себе не breaking, но сигнал для ревью

def breaking_type_change(old_schema, new_schema):
    _, _, old_types = index_properties(old_schema)
    _, _, new_types = index_properties(new_schema)
    breaking = []
    for field, old_t in old_types.items

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

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

Перейти к курсу

Другие статьи

FastAPI для интеграций: версионирование маршрутов и стабильные контракты
fastapi

FastAPI для интеграций: версионирование маршрутов и стабильные контракты

Покажем, как добавлять новые поля, не ломая существующих клиентов, и как аккуратно менять поведение. Рассмотрим стратегии для прод-совместимости.

24 июля 2026 г.
500
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

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

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

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

2 октября 2026 г.
630
Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

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

17 сентября 2026 г.
1050

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что именно “ломает” пользователей: интуитивная модель совместимостиПрямая (backward compatibility) — клиент без изменений продолжает работатьПоведенческая несовместимостьКонтрактная несовместимость схемНесовместимость транспортного уровняВведение в SemVer: как интерпретировать MAJOR/MINOR/PATCH для APIПрактическое правилоЧто считать публичным контрактом в APIЭлементы запросаЭлементы ответаСемантика и инвариантыPATCH: что можно менять безопасно1) Устранение багов без изменения контракта2) Улучшения наблюдаемости без влияния на контракт3) Критические security fixes при сохранении APIMINOR: добавления, которые остаются совместимымиДобавление новых опциональных полей в ответДобавление новых опциональных query-параметровДобавление новых эндпоинтовДобавление новых значений в enumПоддержка новых content-types при сохранении старыхMAJOR: изменения, требующие слома контракта (или гарантий)1) Переименование или удаление полей2) Изменение типов и форматов3) Изменение обязательности (required/optional)4) Изменение semantics: то, что “значит поле”5) Изменение кодов ответов и договорённостей об ошибках6) Пересмотр правил валидации с “отказом” вместо “допуска”7) Поведенческие контракты на уровне идемпотентности и ретраевКритерий “minor vs major”: чеклист для принятия решенияШаг 1: можно ли представить “старый клиент” как программу, следующую спецификации?Шаг 2: изменения расширяют контракт или сужают?Шаг 3: есть ли эффект “по умолчанию”?Шаг 4: есть ли “скрытая” зависимость клиентов от статусов/ошибок?Шаг 5: что с документацией и версионностью схем?Несовместимости на уровне схем: почему JSON и “просто добавили поле” иногда не спасаютСтрогая типизация и десериализацияИзменение nullabilityИзменение ограничений (constraints)Пример: как сформулировать правила на конкретных артефактах OpenAPIКритическое: совместимость схемПример политики в терминах “API breaking change”Автоматизация: как снизить риск неверного MAJORИдеяМинимальный пример сравнения схем (идея)