Версионирование
Хороший API должен быть версионирован: изменения и новые возможности реализуются в новых версиях API, а не в одной и той же версии. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом, API используются клиентами, код которых вы не контролируете. Поэтому, обратная совместимость (BC) должна по возможности сохраняться. Если ломающее её изменение необходимо, делать его нужно в новой версии API. Существующие клиенты могут продолжать использовать старую, совместимую с ними версию API. Новые или обновлённые клиенты могут использовать новую версию.
Tip: Чтобы узнать больше о выборе версий обратитесь к Semantic Versioning.
Общей практикой при реализации версионирования API является включение номера версии в URL-адрес вызова API-метода.
Например, http://example.com/v1/users
означает вызов API /users
версии 1. Другой способ версионирования API,
получивший недавно широкое распространение, состоит в добавлении номера версии в HTTP-заголовки запроса,
обычно в заголовок Accept
:
// как параметр
Accept: application/json; version=v1
// как тип содержимого, определенный поставщиком API
Accept: application/vnd.company.myapp-v1+json
Оба способа имеют достоинства и недостатки, и вокруг них много споров. Ниже мы опишем реально работающую стратегию версионирования API, которая является некоторой смесью этих двух способов:
- Помещать каждую мажорную версию реализации API в отдельный модуль, чей ID является номером мажорной версии (например,
v1
,v2
). Естественно, URL-адреса API будут содержать в себе номера мажорных версий. - В пределах каждой мажорной версии (т.е. внутри соответствующего модуля) использовать HTTP-заголовок
Accept
для определения номера минорной версии и писать условный код для соответствующих минорных версий.
В каждый модуль, обслуживающий мажорную версию, следует включать классы ресурсов и контроллеров,
обслуживающих эту конкретную версию. Для лучшего разделения ответственности кода вы можете составить общий набор
базовых классов ресурсов и контроллеров, и субклассировать их в каждом отдельно взятом модуле версии. Внутри дочерних классов
реализуйте конкретный код вроде метода Model::fields()
.
Ваш код может быть организован примерно следующим образом:
api/
common/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
modules/
v1/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
Module.php
v2/
controllers/
UserController.php
PostController.php
models/
User.php
Post.php
Module.php
Конфигурация вашего приложения могла бы выглядеть так:
return [
'modules' => [
'v1' => [
'class' => 'app\modules\v1\Module',
],
'v2' => [
'class' => 'app\modules\v2\Module',
],
],
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
],
],
],
];
В результате http://example.com/v1/users
возвратит список пользователей API версии 1, в то время как
http://example.com/v2/users
вернет список пользователей версии 2.
Благодаря использованию модулей код API различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно повторное использование кода между модулями через общие базовые классы и другие разделяемые классы.
Для работы с минорными номерами версий вы можете использовать преимущества согласования содержимого,
предоставляемого поведением [[yii\filters\ContentNegotiator|contentNegotiator]].
Определив тип поддерживаемого содержимого, поведение contentNegotiator
установит значение
свойства [[yii\web\Response::acceptParams]].
Например, если запрос посылается с HTTP-заголовком Accept: application/json; version=v1
, то после согласования содержимого
свойство [[yii\web\Response::acceptParams]] будет содержать значение ['version' => 'v1']
.
На основе информации о версии из acceptParams
вы можете выбирать поведение в действиях, классах ресурсов,
сериализаторах и т.д.
Так как минорные версии требуют поддержания обратной совместимости, будем надеяться, что в вашем коде не так уж много проверок на номер версии. В противном случае велики шансы, что вам нужна новая мажорная версия.