|
|
|
Версионирование
|
|
|
|
===============
|
|
|
|
|
|
|
|
Хороший API должен быть *версионирован*: изменения и новые возможности реализуются в новых версиях API, а не в одной и
|
|
|
|
той же версии. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом,
|
|
|
|
API используются клиентами, код которых вы не контролируете. Поэтому, обратная совместимость (BC) должна по возможности
|
|
|
|
сохраняться. Если ломающее её изменение необходимо, делать его нужно в новой версии API. Существующие клиенты могут
|
|
|
|
продолжать использовать старую, совместимую с ними версию API. Новые или обновлённые клиенты могут использовать новую
|
|
|
|
версию.
|
|
|
|
|
|
|
|
|
|
|
|
Общей практикой при реализации версионирования API является включение номера версии в URL-адрес вызова API-метода.
|
|
|
|
Например, `http://example.com/v1/users` означает вызов API `/users` версии 1. Другой способ версионирования API,
|
|
|
|
получивший недавно широкое распространение, состоит в добавлении номера версии в HTTP-заголовки запроса,
|
|
|
|
обычно в заголовок `Accept`:
|
|
|
|
|
|
|
|
> Tip: Чтобы узнать больше о выборе версий обратитесь к [Semantic Versioning](http://semver.org/).
|
|
|
|
|
|
|
|
Один из типичных способов реализации версионирования — указание версии в URL. Например, `http://example.com/v1/users`
|
|
|
|
соответствует `/users` версии 1.
|
|
|
|
|
|
|
|
Ещё один способ, ставший сейчас популярным — передача версии через заголовок 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
|
|
|
|
```
|
|
|
|
|
|
|
|
Конфигурация вашего приложения могла бы выглядеть так:
|
|
|
|
|
|
|
|
```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` вы можете выбирать поведение в действиях, классах ресурсов,
|
|
|
|
сериализаторах и т.д.
|
|
|
|
|
|
|
|
Так как минорные версии требуют поддержания обратной совместимости, будем надеяться, что в вашем коде не так уж много
|
|
|
|
проверок на номер версии. В противном случае велики шансы, что вам нужна новая мажорная версия.
|