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

Один из распространенных способов реализации API-версий - вставить номер версии в URL-адреса API. Например, http://example.com/v1/users - это конечная точка / users API версии 1.

Еще одним методом API-контроля версий, который набрал силу в последнее время, является размещение номера версии в заголовках HTTP-запроса. Обычно это делается через заголовок Accept:

// via a parameter
Accept: application/json; version=v1
// via a vendor content type
Accept: application/vnd.company.myapp-v1+json

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

  • Поместите каждую основную версию реализации API в отдельный модуль, чей идентификатор является основным номером версии (например, v1, v2). Естественно, URL-адреса API будут содержать основные номера версий.
  • В каждой основной версии (и, следовательно, в соответствующем модуле) используйте заголовок Accept HTTP request для определения номера младшей версии и условного кода записи для соответствующего ответа на второстепенные версии.

Для каждого модуля, обслуживающего основную версию, модуль должен включать классы ресурсов и контроллеров, обслуживающих эту конкретную версию. Чтобы лучше разделить ответственность за код, вы можете хранить общий набор базовых ресурсов и классов контроллеров и подклассифицировать их в каждом отдельном модуле версии. Внутри подклассов реализуйте конкретный код, например 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 вернет список пользователей в версии 1, а http://example.com/v2/users вернет пользователей версии 2.

Благодаря модулям код для различных основных версий может быть хорошо изолирован. Но модули все же позволяют повторно использовать код через модули через общие базовые классы и другие общие ресурсы.

Чтобы иметь дело с младшими номерами версий, вы можете воспользоваться функцией согласования содержимого, обеспечиваемой поведением contentNegotiator. Поведение contentNegotiator устанавливает свойство yii\web\Response::$acceptParams, когда оно определяет, какой тип содержимого должен поддерживать.

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

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

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