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

Версионирование интерфейса - это просто «вежливый» способ убить развернутых клиентов.

Roy Fielding.

Версионность API позволяет изменять поведение различных клиентов. Фреймворк REST предусматривает несколько различных схем версионирования.

Версионность определяется входящим запросом клиента и может быть основана либо на URL запроса, либо на заголовках запроса.

Существует несколько правильных подходов к версионности. :doc:`Non-versioned systems can also be appropriate <https://www.infoq.com/articles/roy-fielding-on-versioning>`** , особенно если вы разрабатываете очень долгосрочные системы с множеством клиентов, не зависящих от вас.

Версионирование с помощью фреймворка REST

Если версионность API включена, атрибут request.version будет содержать строку, соответствующую версии, запрошенной во входящем клиентском запросе.

По умолчанию версионность не включена, и request.version всегда будет возвращать None.

Различное поведение в зависимости от версии

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

def get_serializer_class(self):
    if self.request.version == 'v1':
        return AccountSerializerVersion1
    return AccountSerializer

Обратные URL-адреса для версионных API

Функция reverse, включенная во фреймворк REST, связана со схемой версионности. Вам необходимо убедиться, что вы включили текущий request в качестве аргумента ключевого слова, как например.

from rest_framework.reverse import reverse

reverse('bookings-list', request=request)

Приведенная выше функция будет применять любые преобразования URL, соответствующие версии запроса. Например:

  • Если используется NamespaceVersioning, а версия API - „v1“, то для поиска URL будет использоваться 'v1:bookings-list' , что может привести к URL типа http://example.org/v1/bookings/.

  • Если используется QueryParameterVersioning, а версия API 1.0 , то возвращаемый URL может иметь вид http://example.org/bookings/?version=1.0.

Версифицированные API и сериализаторы с гиперссылками

При использовании стилей сериализации с гиперссылками вместе со схемой версионирования на основе URL обязательно включайте запрос в качестве контекста для сериализатора.

def get(self, request):
    queryset = Booking.objects.all()
    serializer = BookingsSerializer(queryset, many=True, context={'request': request})
    return Response({'all_bookings': serializer.data})

Это позволит всем возвращаемым URL-адресам включать соответствующие версии.

Настройка схемы версионирования

Схема версионности определяется ключом настроек DEFAULT_VERSIONING_CLASS.

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}

Если он не задан явно, значением для DEFAULT_VERSIONING_CLASS будет None. В этом случае атрибут request.version всегда будет возвращать None.

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

class ProfileList(APIView):
    versioning_class = versioning.QueryParameterVersioning

Другие настройки версионности

Следующие ключи настроек также используются для управления версионированием:

  • DEFAULT_VERSION. Значение, которое должно использоваться для request.version, когда информация о версиях отсутствует. По умолчанию None.

  • ALLOWED_VERSIONS. Если установлено, это значение ограничивает набор версий, которые могут быть возвращены схемой версий, и выдает ошибку, если предоставленная версия не входит в этот набор. Обратите внимание, что значение, используемое для установки DEFAULT_VERSION, всегда считается частью набора ALLOWED_VERSIONS (если только оно не None ). По умолчанию установлено значение None.

  • VERSION_PARAM. Строка, которая должна использоваться для любых параметров версионирования, например, в типе медиа или параметрах запроса URL. По умолчанию 'version'.

Вы также можете установить свой класс версионности плюс эти три значения на основе каждого просмотра или каждого набора просмотров, определив свою собственную схему версионности и используя переменные класса default_version , allowed_versions и version_param. Например, если вы хотите использовать URLPathVersioning :

from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView

class ExampleVersioning(URLPathVersioning):
    default_version = ...
    allowed_versions = ...
    version_param = ...

class ExampleView(APIVIew):
    versioning_class = ExampleVersioning

Справочник по API

 AcceptHeaderVersioning

Эта схема требует от клиента указать версию как часть типа медиа в заголовке Accept. Версия включается в качестве параметра медиатипа, дополняющего основной медиатип.

Вот пример HTTP-запроса с использованием стиля версионирования заголовка accept.

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0

В приведенном выше примере запроса атрибут request.version вернет строку '1.0'.

Версионирование на основе заголовков accept является generally considered как :doc:`best practice <https://github.com/interagent/http-api-design/blob/master/en/foundations/require-versioning-in-the-accepts-header>`** , хотя другие стили могут быть подходящими в зависимости от требований клиента.

Использование заголовков accept с медиатипами поставщиков

Строго говоря, медиатип json не указывается как including additional parameters. Если вы создаете хорошо специфицированный публичный API, вы можете рассмотреть возможность использования vendor media type. Для этого настройте свои рендереры на использование рендерера на основе JSON с пользовательским медиатипом:

class BookingsAPIRenderer(JSONRenderer):
    media_type = 'application/vnd.megacorp.bookings+json'

Теперь ваши клиентские запросы будут выглядеть следующим образом:

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0

URLPathVersioning

Эта схема требует, чтобы клиент указывал версию как часть пути URL.

GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json

Ваш URL conf должен включать шаблон, соответствующий версии с ключевым аргументом 'version', чтобы эта информация была доступна схеме версионирования.

urlpatterns = [
    re_path(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    ),
    re_path(
        r'^(?P<version>(v1|v2))/bookings/(?P<pk>[0-9]+)/$',
        bookings_detail,
        name='bookings-detail'
    )
]

NamespaceVersioning

Для клиента эта схема такая же, как URLPathVersioning. Единственное различие заключается в том, как она настраивается в вашем Django-приложении, поскольку она использует интервалы между именами URL, вместо аргументов ключевых слов URL.

GET /v1/something/ HTTP/1.1
Host: example.com
Accept: application/json

При такой схеме атрибут request.version определяется на основе namespace, который соответствует пути входящего запроса.

В следующем примере мы даем набору представлений два различных возможных префикса URL, каждый из которых относится к разным пространствам имен:

# bookings/urls.py
urlpatterns = [
    re_path(r'^$', bookings_list, name='bookings-list'),
    re_path(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
]

# urls.py
urlpatterns = [
    re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
    re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]

И URLPathVersioning, и NamespaceVersioning разумны, если вам нужна простая схема версионирования. Подход URLPathVersioning может быть более подходящим для небольших специальных проектов, а подход NamespaceVersioning, вероятно, проще в управлении для больших проектов.

HostNameVersioning

Схема версионности имени хоста требует от клиента указать запрашиваемую версию как часть имени хоста в URL.

Например, ниже приведен HTTP-запрос к URL http://v1.example.com/bookings/:

GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json

По умолчанию эта реализация ожидает, что имя хоста будет соответствовать этому простому регулярному выражению:

^([a-zA-Z0-9]+)**.[a-zA-Z0-9]+**.[a-zA-Z0-9]+$

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

Схема HostNameVersioning может быть неудобна для использования в режиме отладки, поскольку обычно вы получаете доступ к необработанному IP-адресу, например 127.0.0.1. Существуют различные онлайн-уроки по работе со схемой access localhost with a custom subdomain, которые могут оказаться полезными в этом случае.

Версионность на основе имени хоста может быть особенно полезна, если у вас есть требования направлять входящие запросы на разные серверы в зависимости от версии, поскольку вы можете настроить разные записи DNS для разных версий API.

QueryParameterVersioning

Эта схема представляет собой простой стиль, который включает версию в качестве параметра запроса в URL. Например:

GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json

Пользовательские схемы версионирования

Чтобы реализовать пользовательскую схему версионирования, подкласс BaseVersioning и переопределите метод .determine_version.

Пример

В следующем примере используется пользовательский заголовок X-API-Version для определения запрашиваемой версии.

class XAPIVersionScheme(versioning.BaseVersioning):
    def determine_version(self, request, *args, **kwargs):
        return request.META.get('HTTP_X_API_VERSION', None)

Если ваша схема версионирования основана на URL запроса, вы также захотите изменить способ определения версионированных URL. Для этого вам следует переопределить метод .reverse() в классе. Примеры см. в исходном коде.

Вернуться на верх