rest-versioning.md 7.03 KB
Newer Older
1 2
Версионирование
===============
3

4 5 6 7 8
Свои API вам следует версионировать. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом,
при работе с API у вас обычно нет контроля над клиентским кодом, работающим с вашими API. Поэтому всегда, когда это только возможно, 
должна поддерживаться обратная совместимость API, и если в API должны быть внесены изменения, ломающие обратную совместимость,
следует увеличить номер версии. Почитайте про [семантическое версионирование](http://semver.org/)
для получения более подробной информации о возможных способах нумерации различных версий ваших API.
9

10 11 12 13
Общей практикой при реализации версионирования API является включение номера версии в URL-адрес вызова API-метода.
Например, `http://example.com/v1/users` означает вызов API `/users` версии 1. Другой способ версионирования API,
получивший недавно широкое распространение, состоит в добавлении номера версии в HTTP-заголовки запроса, 
обычно в заголовок `Accept`:
14 15

```
16
// как параметр
17
Accept: application/json; version=v1
18
// как тип содержимого, определенный поставщиком API
19 20 21
Accept: application/vnd.company.myapp-v1+json
```

22 23
Оба способа имеют достоинства и недостатки, и вокруг них много споров. Ниже мы опишем реально работающую стратегию
версионирования API, которая является некоторой смесью этих двух способов:
24

25 26 27 28
* Помещать каждую мажорную версию реализации API в отдельный модуль, чей ID является номером мажорной версии (например, `v1`, `v2`).
  Естественно, URL-адреса API будут содержать в себе номера мажорных версий.
* В пределах каждой мажорной версии (т.е. внутри соответствующего модуля) использовать HTTP-заголовок `Accept`
  для определения номера минорной версии и писать условный код для соответствующих минорных версий.
29

30 31 32 33
В каждый модуль, обслуживающий мажорную версию, следует включать классы ресурсов и контроллеров,
обслуживающих эту конкретную версию. Для лучшего разделения ответственности кода вы можете составить общий набор 
базовых классов ресурсов и контроллеров, и субклассировать их в каждом отдельно взятом модуле версии. Внутри дочерних классов
реализуйте конкретный код вроде метода `Model::fields()`.
34

35
Ваш код может быть организован примерно следующим образом:
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

```
api/
    common/
        controllers/
            UserController.php
            PostController.php
        models/
            User.php
            Post.php
    modules/
        v1/
            controllers/
                UserController.php
                PostController.php
            models/
                User.php
                Post.php
        v2/
            controllers/
                UserController.php
                PostController.php
            models/
                User.php
                Post.php
```

63
Конфигурация вашего приложения могла бы выглядеть так:
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

```php
return [
    'modules' => [
        'v1' => [
            'basePath' => '@app/modules/v1',
        ],
        'v2' => [
            'basePath' => '@app/modules/v2',
        ],
    ],
    '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']],
            ],
        ],
    ],
];
```

89 90
В результате `http://example.com/v1/users` возвратит список пользователей API версии 1, в то время как
`http://example.com/v2/users` вернет список пользователей версии 2.
91

92 93
При использовании модулей код API различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно
повторное использование кода между модулями через общие базовые классы и другие разделяемые классы.
94

95 96 97 98
Для работы с минорными номерами версий вы можете использовать преимущества согласования содержимого,
предоставляемого поведением [[yii\filters\ContentNegotiator|contentNegotiator]].
Определив тип поддерживаемого содержимого, поведение `contentNegotiator` установит значение 
свойства [[yii\web\Response::acceptParams]].
99

100 101
Например, если запрос посылается с HTTP-заголовком `Accept: application/json; version=v1`, то после согласования содержимого
свойство [[yii\web\Response::acceptParams]] будет содержать значение `['version' => 'v1']`.
102

103 104
На основе информации о версии из `acceptParams` вы можете написать условный код в таких местах,
как действия, классы ресурсов, сериализаторы и т.д.
105

106 107
Так как минорные версии требуют поддержания обратной совместимости, будем надеяться, что в вашем коде не так уж много
проверок на номер версии. В противном случае велики шансы, что вам нужна новая мажорная версия.