# Версионирование ¶

Хороший API должен быть версионирован: изменения и новые возможности реализуются в новых версиях API, а не в одной и той же версии. В отличие от Web-приложений, где у вас есть полный контроль и над серверным, и над клиентским кодом, API используются клиентами, код которых вы не контролируете. Поэтому, обратная совместимость (BC) должна по возможности сохраняться. Если ломающее её изменение необходимо, делать его нужно в новой версии API. Существующие клиенты могут продолжать использовать старую, совместимую с ними версию API. Новые или обновлённые клиенты могут использовать новую версию.

> Подсказка: Чтобы узнать больше о выборе версий обратитесь к 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 различных мажорных версий может быть хорошо изолирован. И по-прежнему возможно повторное использование кода между модулями через общие базовые классы и другие разделяемые классы.

Для работы с минорными номерами версий вы можете использовать преимущества согласования содержимого, предоставляемого поведением contentNegotiator. Определив тип поддерживаемого содержимого, поведение `contentNegotiator` установит значение свойства yii\web\Response::$acceptParams.

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

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

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