Этап 6 - Версионирование API
Версионирование нужно потому, что клиент и сервер выкатываются независимо. Мобильное приложение, партнерская интеграция или frontend-релиз могут продолжать вызывать старый контракт еще долго после изменения backend. Версия должна защищать клиента от breaking changes, а не становиться мусорным ящиком для каждого мелкого изменения. Добавление нового необязательного поля обычно не требует новой версии. Удаление поля, смена смысла, переименование параметра или изменение формата ошибки обычно требует.

Какую проблему это решает
API design становится важным, когда от одного backend зависят несколько клиентов. Метод controller может быть простым в коде и неудобным в использовании. Клиенту нужны названия из предметной области, стабильные response bodies, предсказуемые ошибки и endpoints для списков, которые не ломаются при росте данных. Хороший дизайн API отличает сервер, который просто отвечает, от интерфейса продукта, на который команды могут безопасно опираться.
Полезно думать об этой теме через контракт. Контракт - это не только сгенерированная документация. Это обещание: request с конкретным method, path, parameters, body, authentication state и headers получит response с понятным status code и известной формой. Если backend developer меняет это обещание без осторожности, frontend, mobile app, партнерская интеграция или автоматическая job могут сломаться, хотя сервер продолжит запускаться.
Как об этом думать
Начинайте с бизнес-действия, а потом переводите его на язык API. Если пользователь хочет видеть заказы, ресурс скорее всего называется orders, а не getOrdersData. Если пользователь хочет отменить заказ, нужно понять: это изменение состояния существующего заказа, sub-resource или доменная команда, которой нужен отдельный endpoint. Решение должно быть видно в URL, method, response code и формате ошибки.
Не проектируйте “умно”. Проектируйте понятно для разработчика, который откроет API через полгода. Имена должны быть скучными и очевидными. Optional parameters должны иметь ясные значения по умолчанию. Response должен включать только то, что клиенту разрешено знать. Внутренние имена колонок, stack traces, экспериментальные enum и временные поля миграции не должны вытекать в публичный контракт случайно.
Конкретный пример
GET /api/orders?status=PAID&page=0&size=20&sort=createdAt,desc
Accept: application/json
Такой endpoint рассказывает понятную историю. Клиент просит orders, а не вызывает backend function по имени. Query parameters явные: один filter, запрос страницы и правило sorting. Сервер может проверить эти параметры, описать их в OpenAPI, вернуть стабильную форму ответа и использовать единый формат ошибки, если request неправильный.
Полезная таблица
| Понятие | Значение |
|---|---|
| breaking change | Изменение, которое может сломать клиента |
| additive change | Безопасное необязательное добавление |
| URI version | Версия в path |
| deprecation | Планируемое удаление с временем на миграцию |
Частые ошибки
- Проектировать endpoints от имен controller methods, а не от ресурсов.
- Возвращать наружу то, как сейчас выглядит database entity.
- Считать validation, errors, pagination и versioning деталями, которые можно добавить потом.
- Менять форму response без проверки существующих clients.
- Документировать API только после implementation, когда дизайн уже трудно менять.
Чеклист понимания
- Я могу объяснить, какой контракт endpoint дает клиенту.
- Я могу назвать request parameters и допустимые values.
- Я могу описать успешный и ошибочный response без чтения backend code.
- Я понимаю, какие изменения будут breaking для существующего client.
Практика перед следующим уроком
Разделите пять изменений API на breaking и non-breaking и решите, нужна ли новая версия.