rzaripov.kz

Версионирование API в PHP: REST и GraphQL

Версионирование API определяет, как сервер будет развиваться после публикации первой стабильной версии. Клиентские приложения редко обновляются одновременно с backend: мобильная программа может оставаться на устройстве месяцами, а интеграция с внешней системой — работать годами. Поэтому изменение формата ответа, удаление поля или пересмотр бизнес-правила требуют заранее продуманной стратегии совместимости.

В PHP выбор подхода особенно заметен в проектах на Laravel, Symfony и чистом HTTP-стеке. REST обычно связывает версию с адресом ресурса или заголовками, тогда как GraphQL предлагает единую точку входа и эволюцию типизированной схемы. Оба варианта способны обеспечить устойчивый контракт, но по-разному распределяют ответственность между сервером и клиентом.

Зачем API нужна отдельная версия

Версия API фиксирует набор правил, на который может рассчитывать потребитель. К таким правилам относятся имена полей, типы данных, обязательность параметров, коды ошибок, способы авторизации и поведение отдельных операций. Даже небольшое изменение, например превращение строки в массив, способно нарушить десериализацию в мобильном приложении.

В REST границы контракта видны через маршруты: /api/v1/users, /api/v2/users, отдельные методы и форматы ответов. Такой подход легко объяснить команде и внешним заказчикам. Для старой версии можно сохранить прежний контроллер, DTO и правила валидации, а новые требования реализовать параллельно.

Однако отдельная ветка API увеличивает стоимость сопровождения. Исправление ошибки безопасности или изменение общей модели приходится переносить в несколько версий. При слабой дисциплине старые маршруты постепенно превращаются в самостоятельные продукты с разной логикой, документацией и уровнем качества.

REST с версиями в маршрутах и заголовках

Самый распространённый вариант для PHP — версия в URL. В Laravel она оформляется группой маршрутов и префиксом, а в Symfony может быть реализована через атрибуты, отдельные контроллеры или собственный механизм разрешения маршрутов. Например, /api/v1/orders и /api/v2/orders могут возвращать разные представления заказа, сохраняя общую доменную модель внутри приложения.

Преимущество URL-версии — прозрачность. Адрес легко открыть в браузере, проверить через curl, добавить в документацию OpenAPI и использовать в журнале запросов. Кэширование на уровне CDN или reverse proxy также становится понятнее: версия включена в ключ ресурса, а старые ответы не смешиваются с новыми.

Версионирование через заголовки выглядит аккуратнее с точки зрения адресов. Клиент передаёт Accept: application/vnd.company.v2+json или специальный заголовок, а сервер выбирает нужный сериализатор. Такой способ отделяет идентичность ресурса от представления, но требует хорошей настройки прокси, Vary-заголовков, документации и инструментов тестирования. Ошибка в конфигурации кэша может привести к выдаче ответа одной версии другому клиенту.

Существует и смешанная стратегия: основные несовместимые изменения получают новую версию URL, а небольшие варианты представления управляются заголовками или параметрами запроса. Важно заранее определить, что считается breaking change. Добавление необязательного поля обычно безопасно, а переименование свойства, изменение семантики статуса или удаление эндпоинта уже требует миграционного периода.

Критерий REST GraphQL
Главный механизм эволюции Версии URL, заголовков или медиатипов Версия схемы и постепенное устаревание полей
Точка входа Набор ресурсов и маршрутов Обычно один endpoint
Контроль ответа Определяется сервером Клиент выбирает поля в запросе
Удаление поля Часто требует новой версии Поле помечается deprecated и удаляется позже
Кэширование Хорошо поддерживается HTTP-инфраструктурой Требует сложной настройки по запросам
Риск сложной поддержки Дублирование версий контроллеров Рост схемы и накопление устаревших полей
Удобство для мобильных клиентов Высокое при стабильных DTO Высокое при грамотной схеме и code generation

GraphQL и эволюция типизированной схемы

GraphQL обычно работает через один endpoint, например /graphql, поэтому классическая схема /v1 и /v2 не является обязательной. Клиент сам указывает нужные поля, а сервер проверяет запрос по схеме. Если в объект добавляется новое необязательное поле, уже существующие запросы продолжают работать без изменения версии.

Ключевой механизм совместимости в GraphQL — директива @deprecated. Поле или аргумент помечают как устаревшие, указывают причину и оставляют в схеме на переходный период. Клиенты получают сигнал от IDE, генератора типов или проверки схемы, после чего команда может перевести их на новое поле. Это делает отказ от старой структуры постепенным.

У такого подхода есть пределы. Изменение типа существующего поля, смена смысла значения, ужесточение обязательности аргумента или изменение правил авторизации остаются потенциально разрушающими. GraphQL не отменяет управление контрактом: при крупных изменениях можно публиковать отдельную схему, использовать заголовок для выбора версии или поддерживать несколько endpoint.

Для PHP важна и серверная реализация резолверов. Схема может быть описана через SDL, PHP-атрибуты или конфигурацию библиотеки. Если резолверы напрямую обращаются к ORM, обновление схемы иногда скрывает проблемы производительности: один запрос клиента способен вызвать множество обращений к базе. Поэтому версионирование нужно рассматривать вместе с DataLoader, ограничением глубины запроса, анализом сложности и кэшированием.

Выбор подхода для PHP-проекта

REST часто лучше подходит для публичного API, CRUD-сервисов, интеграций с платёжными системами и приложений, где важны стандартные HTTP-коды, идемпотентность и независимое кэширование ресурсов. Его проще подключить к системам мониторинга, API Gateway и документации OpenAPI. Для команды с привычным MVC-подходом порог внедрения также ниже.

GraphQL выигрывает там, где интерфейсам нужны разные срезы одной предметной области. В мобильном приложении экран может запросить компактную модель, а веб-клиент — расширенную, не создавая отдельные REST-методы для каждого варианта. Это уменьшает число лишних запросов и позволяет клиенту контролировать объём данных, хотя требует строгих правил схемы.

Кроссплатформенная разработка добавляет ещё один критерий. Android-, iOS- и desktop-клиенты могут обновляться с разной скоростью, поэтому для них полезны автогенерация моделей, контрактные тесты и явная политика поддержки. Например, PHP-сервер может публиковать схему GraphQL или OpenAPI в CI, а команды Kotlin и Swift получают проверяемые типы вместо ручного копирования DTO.

Для контроля регрессий подойдут автоматические проверки контрактов и сценариев, включая Test Detective. Важен сам процесс: тесты должны подтверждать допустимые ответы старых клиентов, проверять коды ошибок и фиксировать момент, когда устаревшая операция перестаёт соответствовать объявленной политике.

Миграция, документация и жизненный цикл

Перед выпуском новой версии стоит собрать карту потребителей: мобильные приложения, веб-интерфейсы, партнёрские сервисы, фоновые задачи и внутренние скрипты. Для каждого клиента полезно знать используемые поля, частоту запросов и минимальную поддерживаемую версию. В REST это можно получить из логов маршрутов, а в GraphQL — из анализа операций и статистики по полям.

Затем публикуется политика совместимости. В ней указывают срок поддержки, правила добавления и удаления полей, формат ошибок, требования к заголовкам и дату отключения устаревших методов. Предупреждение должно появляться заранее: через Deprecation и Sunset в HTTP, сообщение в GraphQL-схеме, документацию и мониторинг фактического использования.

Для REST безопасная миграция часто выглядит так: сначала добавить новый ресурс или поле, затем обновить клиентов, перевести трафик, ограничить старый маршрут и только после этого удалить его. Для GraphQL последовательность похожа: добавить замену, пометить прежнее поле как устаревшее, найти активных потребителей, дождаться перехода и удалить старый элемент из схемы.

Производительность также входит в контракт эксплуатации. Изменение ответа может повлиять на размер мобильного трафика, время сериализации и расход памяти PHP-FPM. Опыт оптимизации изображений в приложениях Kotlin, описанный в материале об оптимизации загрузки изображений, хорошо иллюстрирует общий принцип: формат данных нужно оценивать вместе с реальными условиями клиента, сетью и стоимостью обработки.

Выбор между REST и GraphQL определяется архитектурой продукта, числом потребителей и характером изменений. REST даёт ясные границы версий и опирается на зрелую HTTP-инфраструктуру. GraphQL сокращает необходимость в многочисленных версиях, если команда умеет управлять схемой, устаревшими полями и нагрузкой. В обоих случаях устойчивость API формируется регулярными контрактными тестами, наблюдаемостью и заранее объявленным жизненным циклом.