Модуль 5 · Версии и изменения

Справочник «Стандартизация HTTP API» · модуль 5 · Версии и изменения

Глава 5.4

5.4

CHANGELOG
и ADR

Описание OpenAPI отвечает на вопрос «как API устроен сейчас». Два вопроса оно не закрывает: что изменилось с прошлого раза и почему сделано именно так. На них отвечают два текстовых файла, которые лежат в репозитории рядом с кодом.

Категория
Документы
Уровень
Базовый
Связанные главы
5.1 Четыре номера · 5.3 Deprecation и Sunset · 7.1 Шаблон pull request
Зачем это знать
Чтобы решения и изменения не жили только в чьей-то памяти

§1Два вопроса, два документа

CHANGELOG.md — «что изменилось»
  • читатель: потребитель API
  • единица записи: выпуск версии
  • отвечает: нужно ли мне что-то делать
  • пишется при каждом выпуске
ADR — «почему так сделано»
  • читатель: разработчик, в том числе будущий
  • единица записи: одно решение
  • отвечает: какие были варианты и чем платим
  • пишется один раз и не меняется

Ни один из них не заменяет описание OpenAPI и не заменяет друг друга. Описание — снимок настоящего, журнал — история, ADR — обоснование. Если какого-то из трёх документов нет, ответ на соответствующий вопрос приходится восстанавливать по истории коммитов.

§2Keep a Changelog

Формат журнала брать с нуля не нужно — есть общепринятый: Keep a Changelog. В учебном проекте ссылка на него стоит прямо в шапке файла.

CHANGELOG.md · шапка
# Changelog

Формат — Keep a Changelog, версии — Semantic Versioning.
Журнал написан для потребителей API: здесь только изменения публичного контракта.

## [Unreleased]

## [1.1.0] - 2026-09-16
Новое сверхуСвежая версия первой: читателю нужна она, а не история пятилетней давности.порядок
Раздел UnreleasedКопилка для изменений, уже объединённых в основную ветку, но ещё не выпущенных. При выпуске превращается в номер версии с датой.копилка
Номер и датаНомер связывает запись с info.version в описании, дата отвечает на вопрос «когда это стало правдой».привязка

§3Журнал пишут для потребителя

Чаще всего журнал изменений пишут как список коммитов. Сравним две записи об одном и том же изменении:

Так не надо
  • refactor: вынес middleware в отдельный модуль
  • fix: поправил тесты после мержа
  • chore: обновил зависимости
Так надо
  • Заголовок X-Request-Id в каждом ответе; значение клиента возвращается, если оно корректно.
  • ETag в ответах с одним обращением, 304 на совпавший If-None-Match.
  • Лимит операций записи: 429 с Retry-After.

Проверяют это так: возьмите строку журнала и определите — может ли внешний разработчик, не видевший ваш код, понять, изменится ли что-то для него? «Вынес middleware в модуль» этой проверки не проходит: снаружи не изменилось ничего. «ETag в ответах» — проходит: клиент может начать им пользоваться.

§4Шесть разделов записи

РазделЧто сюда пишутВлияние на SemVer
AddedНовые возможности: операция, параметр, заголовок, полеMINOR
ChangedИзменения в существующем поведенииMINOR или MAJOR
DeprecatedЧто объявлено устаревшим, чем заменить, когда отключатMINOR
RemovedЧто убрано окончательноMAJOR
FixedИсправления поведения, не меняющие обещанийPATCH
SecurityИсправления, связанные с безопасностьюPATCH или MINOR

Правый столбец нужен на практике: раздел, в который попало изменение, почти определяет, какую часть номера повышать. Запись в Removed означает MAJOR и, значит, новый путь /api/v2. Если вам не хочется менять мажорную версию, изменение нужно не переименовывать, а переделывать — например, в устаревание из главы 5.3.

настоящая запись учебного проекта
## [1.1.0] - 2026-09-16

### Added
- Заголовок `X-Request-Id` в каждом ответе; значение клиента возвращается, если оно корректно.
- `ETag` в ответах с одним обращением, `304 Not Modified` на совпавший `If-None-Match`.
- `If-Match` в `PATCH /api/v1/tickets/{ticket_id}`, ответ `412` при устаревшей версии.
- `Idempotency-Key` в `POST /api/v1/tickets`: повтор с тем же ключом не создаёт второе обращение.

### Deprecated
- `GET /api/v1/tickets/open` — используйте `GET /api/v1/tickets?status=open`.
  Ответ содержит `Deprecation` и `Sunset`; отключение 2027-03-01.

Разделов Removed и Changed здесь нет, потому что в версии 1.1.0 ничего не убирали и не меняли, только добавляли. Поэтому версия MINOR, а не MAJOR.

§5Как это делают другие

Страница журнала изменений API Stripe с разбивкой по версиям-датам
Stripe. Журнал ведётся по версиям-датам, у каждой записи указано, ломающая она или нет. Для платёжного API это главный вопрос читателя, поэтому ответ вынесен вперёд.
Файл CHANGELOG в репозитории Kubernetes с разделами по видам изменений
Kubernetes. Журнал разбит по компонентам и видам изменений, отдельно выделены «Urgent Upgrade Notes» — то, что читателю нужно сделать руками. Формат крупнее учебного, идея та же: читатель должен понять, что делать.

В обоих случаях запись сообщает, что изменение означает для клиента. Различается подробность: чем больше у API клиентов, тем она выше.

§6ADR: запись решения

ADR — Architecture Decision Record, запись архитектурного решения. Один файл — одно решение. Формат нужен потому, что доводы, звучавшие при обсуждении, со временем забываются, и следующее обсуждение начинается без них.

В учебном проекте такой файл один — docs/adr/0001-api-versioning.md. Он отвечает на вопрос «почему версия в пути, а не в заголовке».

docs/adr/0001-api-versioning.md · шапка и контекст
# ADR 0001. Мажорная версия API в пути URL

- Статус: принято
- Дата: 2026-09-15

## Context

Service Desk API используют веб-клиент поддержки и мобильное приложение сотрудников.
Мобильное приложение обновляется у пользователей неделями, поэтому после несовместимого
изменения старая и новая редакции контракта должны работать одновременно.

Рассматривались три варианта:
1. мажорная версия в пути: /api/v1/tickets;
2. версия в заголовке: X-Api-Version, как в GitHub REST API;
3. версия в media type: Accept: application/vnd.servicedesk.v1+json.

Обратите внимание на две детали. Первая: перечислены отвергнутые варианты, без которых решение нельзя обсуждать по существу. Вторая: контекст объясняет, почему вопрос вообще возник, — из-за медленно обновляющегося мобильного приложения. Если это обстоятельство изменится, решение можно пересмотреть.

§7Четыре части ADR

СтатусПредложено, принято, заменено. Заменённое решение не удаляют: на него ссылается новое.где мы
ContextОбстоятельства и ограничения, из-за которых вопрос встал. Пишется без выводов.почему вопрос
DecisionЧто решили — одним-двумя предложениями, в настоящем времени.что решили
ConsequencesПоследствия, и хорошие, и плохие. Раздел, ради которого всё и пишется.чем платим
тот же файл · решение и последствия
## Decision
Мажорная версия указывается в пути: /api/v1. Версия контракта ведётся по
Semantic Versioning в info.version.

## Consequences
- Версия видна в любом логе, ссылке и в адресной строке Swagger UI.
- Старую и новую редакции можно направить на разные экземпляры сервиса правилом gateway.
- Кэши и CDN различают редакции без настройки Vary.
- Нельзя выпустить несовместимое изменение одной операции, не выпуская /api/v2 целиком.
- Для перехода на заголовочную схему, если она понадобится, потребуется новый ADR.

Четвёртый пункт — минус выбранного решения, записанный самим автором. Документ, в котором перечислены только достоинства, не позволяет понять, при каких условиях решение перестанет подходить.

ADR не редактируют

Если решение изменилось, заводят новый файл со ссылкой на прежний, а у прежнего ставят статус «заменено». По отредактированному задним числом ADR нельзя понять, что было известно в момент принятия решения.

§8Когда заводить ADR

Стоит завести

Выбор способа версионирования, формата ошибок, схемы аутентификации, политики совместимости, формата пагинации — всё, что потом дорого менять и о чём будут спрашивать.

Не нужно

Имя переменной, выбор библиотеки для внутренней задачи, порядок полей в ответе — то, что меняется без последствий для клиентов.

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

В этом справочнике ADR встречается трижды: он обосновал версию в пути (глава 5.1), на него ссылается политика совместимости (глава 5.2) и он же будет нужен при выборе схемы аутентификации в модуле 7. На каждое решение заводится отдельный файл.

§9Частые ошибки

Журнал из списка коммитовЧитателю не нужны ваши рефакторинги — ему нужно знать, изменилось ли что-то для него.
Запись без указания заменыВ разделе Deprecated обязательны и замена, и дата отключения.
Журнал, который пишут раз в полгодаЗапись делают тем же изменением, что и правку контракта, иначе она не появится никогда.
ADR только с достоинствамиРаздел последствий без минусов бесполезен: именно минусы объясняют будущие проблемы.
Редактировать старый ADRРешение изменилось — заводят новый файл и ставят прежнему статус «заменено».
Обсуждать решение в переписке и нигде не записатьЧерез год не останется ни доводов, ни отвергнутых вариантов, и спор начнётся с нуля.

§10Проверьте себя

  1. На какие два вопроса отвечают CHANGELOG и ADR и чем оба отличаются от описания OpenAPI?
  2. Как проверить, что строка журнала написана для потребителя, а не для команды?
  3. Почему запись в разделе Removed почти всегда означает новую мажорную версию?
  4. Зачем в журнале нужен раздел Unreleased?
  5. Назовите четыре части ADR и скажите, что записывают в разделе последствий.
  6. Что делают, если решение, описанное в ADR, изменилось?
CHANGELOGKeep a Changelogновое сверхуUnreleasedдля потребителя API
РазделыAdded · ChangedDeprecated · RemovedFixed · Security
ADRСтатусContextDecisionConsequences
Правилаодин файл — одно решениеминусы записыватьне редактировать

Справочник «Стандартизация HTTP API» · модуль 5 · глава 5.4Макаров Максим Николаевич

Кадр