Семантическое версионирование
Семантическое версионирование
1) Что такое SemVer и зачем он нужен
SemVer задает предсказуемые правила присвоения версий артефактам (библиотекам, API, сервисам, схемам), чтобы потребители понимали, чего ожидать от обновления:- MAJOR — несовместимые изменения (breaking changes).
- MINOR — новая функциональность, совместимая по API.
- PATCH — обратимо-совместимые исправления дефектов.
Цель: договориться о стабильности контрактов и снизить стоимость обновлений.
2) Формат версии
Базовый формат:- `MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]`
`1.4.7` — стабильный релиз.
`1.5.0-rc.2` — пре-релиз (release candidate №2).
`2.0.0+linux.arm64` — build-метаданные (не влияют на сравнение версий).
1. Сначала сравниваются `MAJOR`, затем `MINOR`, затем `PATCH`.
2. Пре-релизы меньше соответствующей стабильной версии: `1.2.0-rc.1 < 1.2.0`.
3. Build-метаданные (`+…`) на порядок не влияют: `1.2.0+001 == 1.2.0`.
3) Что считается breaking change
Breaking change — любое изменение, требующее действий потребителя:- Удаление/переименование/смена сигнатуры публичных методов/эндпоинтов.
- Изменение формата входа/выхода (JSON-схема, типы).
- Изменение контрактов ошибок (коды/структуры).
- Изменение side-effects/SLAs (например, строгие лимиты или новые обязательные поля).
Не breaking (при корректной реализации): добавление необязательных полей, расширение enum новыми значениями (если клиент их игнорирует), новые эндпоинты, новые флаги с дефолтами, не влияющими на текущие вызовы.
4) Пре-релизы и канальные стратегии
Пре-релизы позволяют тестировать без нарушения SemVer-обещаний:- Метки: `alpha`, `beta`, `rc`. Пример: `2.3.0-beta.3`.
- Каналы: nightly → alpha → beta → rc → stable.
- Политика: пре-релизы не должны попадать как транзитивные зависимости для прод-сборок по умолчанию.
5) Диапазоны версий и точность зависимостей
В реальных экосистемах используются выражения диапазонов:5.1 Node/npm (SemVer по умолчанию)
`^1.4.2` ≈ `>=1.4.2 <2.0.0` (разрешает MINOR/PATCH, фиксирует MAJOR).
`~1.4.2` ≈ `>=1.4.2 <1.5.0` (разрешает PATCH в пределах MINOR).
`1.4.x` — любая заплатка в 1.4.
Точный пин: `1.4.2`.
5.2 Python (PEP 440, pip)
`~=1.4.2` ≈ `>=1.4.2,==1.4.`.
`>=1.4,<2.0` — явные границы.
5.3 Maven/Gradle (Java)
`[1.4,2.0)` — включительно/исключительно.
Рекомендуется строгое пинование для прод-критичных артефактов.
5.4 Go modules
Всегда полные теги `vMAJOR.MINOR.PATCH`; `v2+` требует суффикс модуля `/v2`.
Рекомендация: для приложений — точные пины (reproducible builds). Для библиотек — caret-диапазоны (облегчить обновления без ломок).
6) CHANGELOG и Conventional Commits
Структурированный журнал изменений повышает прозрачность.
Conventional Commits:
feat(payments): add PIX refund endpoint fix(api): correct 400 → 422 on invalid payload perf(cache): reduce p99 by 20%
refactor(core): extract rule engine docs: update API usage examples chore(deps): bump lodash to 4. 17. 21 feat!: remove legacy webhook v1 (BREAKING CHANGE:...)
Типы: `feat`, `fix`, `perf`, `docs`, `refactor`, `chore` и т. д.
Восклицательный знак или блок `BREAKING CHANGE` объявляет повышение MAJOR.
CHANGELOG генерируется из истории коммитов (release-notes ботами).
7) Политика версионирования для API
Публичные API: строгое SemVer; breaking → MAJOR.
HTTP/REST: версионирование по URL/заголовку: `/v1/...`, `/v2/...` или `Accept: application/vnd.org.service.v2+json`.
JSON-схемы: минорные расширения — новые необязательные поля; major — удаление/изменение обязательных.
gRPC/Protobuf: добавляйте новые поля с новыми номерами; не переиспользуйте номера полей; удаление → deprecate, а не «ломать» существующие.
8) Схемы данных и миграции
Миграции БД синхронизируются с версиями приложения: `app@1.8.0` требует `schema@1.8.x`.
Для breaking изменений схемы — фазы: expand (добавить), migrate, contract (удалить). Версию повышайте до MAJOR только когда удаляете старый контракт.
Поддерживайте двойную запись/чтение на время миграции.
9) Монорепо и микросервисы
Multi-package: каждому пакету свой `MAJOR.MINOR.PATCH`; общий корневой цикл релизов только для мета-артефактов.
Варьируйте стратегии:- Independent versions (Lerna/Changesets) — усиливает изоляцию.
- Lock-step — проще коммуникация, но больше ложных MAJOR-ов.
- Для микросервисов фиксируйте контракты (OpenAPI/Protobuf) с отдельной версией: `contract@2.1.0`, сервис следует ей.
10) Автоматизация релизов в CI/CD
Автовычисление версии на основе Conventional Commits:- `fix` → `PATCH`, `feat` → `MINOR`, `!`/`BREAKING` → `MAJOR`.
yaml
Pseudo-workflow steps:
- run: npx semantic-release
- run: git tag v$NEW_VERSION && git push --tags
- run: cosign sign ghcr. io/org/app:v$NEW_VERSION
Генерация CHANGELOG, публикация релиз-ноутов, обновление зависимостей в GitOps-репо, проверка, что `main` всегда указывает на последний стабильный тег.
11) Политика деприваций (deprecation policy)
Объявление: помечайте функционал как deprecated в MINOR-релизе, давайте срок EOL (например, 90 дней).
Наблюдаемость: логируйте использование устаревших эндпоинтов с user/tenant-контекстом.
Удаление: в следующем MAJOR. Документируйте путь миграции.
12) Примеры и шаблоны
12.1 Регулярное выражение валидации SemVer
regex
^(0 [1-9]\d)\.(0 [1-9]\d)\.(0 [1-9]\d)(?--([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)))? (?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)))?$
12.2 Примеры сравнений
`1.2.3` < `1.10.0` (сравнение по MINOR).
`2.0.0-rc.1` < `2.0.0`.
`1.2.3+build.5` == `1.2.3`.
12.3 Политика зависимостей (пример YAML)
yaml policy:
libraries:
default_range: "^MAJOR. MINOR. PATCH"
pin_security_critical: true services:
pin_exact: true allow_prerelease_in_nonprod: true api_contract:
require_same_minor: true forbid_major_mismatch: true
13) Анти-паттерны
Использование `latest`/плавающих тегов в проде.
Повышение MAJOR без реальных ломок («маркетинговые версии»).
Скрытые breaking-изменения под видом `PATCH`.
Пре-релизы в транзитивных зависимостях прод-приложений.
Менять артефакт без нового тега (мутабельные версии).
Несогласованные версии кода и схемы БД.
14) Чек-лист внедрения (0–45 дней)
0–10 дней
Примите SemVer как обязательный стандарт, утвердите критерии ломок.
Включите Conventional Commits и шаблон PR с полем `BREAKING CHANGE`.
11–25 дней
Подключите semantic-release/changesets, автогенерацию CHANGELOG.
Настройте подпись и публикацию артефактов по тегу `vX.Y.Z`.
26–45 дней
Введите deprecation policy и телеметрию использования устаревших API.
Синхронизируйте версии контрактов (OpenAPI/Proto) и сервисов.
Запретите `latest` и мутабельные теги на уровне политики (OPA/регламенты CI).
15) Метрики зрелости
% артефактов, издаваемых только по тегу SemVer.
Среднее время миграции между MINOR-версиями.
Количество инцидентов из-за скрытых breaking-изменений.
Покрытие Conventional Commits в репозиториях (>95%).
Доля зависимостей без плавающих диапазонов в проде (>90%).
16) Когда SemVer не нужен
Внутренние быстрые прототипы без внешних потребителей (подойдет датированное версионирование).
Данные/модели с экспериментальными фичами (лучше Model/Schema Versioning с совместимостью на уровне конвертеров).
Контент-пакеты без стабильного публичного API.
17) Заключение
SemVer — это контракт доверия между производителем и потребителем. Четко определяйте, что ломает совместимость, автоматизируйте распознавание типов релизов и публикацию артефактов, ведите прозрачный CHANGELOG и соблюдайте политику деприваций. Тогда обновления станут рутинными, прогнозируемыми и безопасными — а инфраструктура и API будут развиваться без шоков для бизнеса.