Версионирование интерфейса - это просто "вежливый" способ убить развернутых клиентов.
Версионность API позволяет изменять поведение различных клиентов. DRF предусматривает несколько различных схем версионирования.
Версионность определяется входящим запросом клиента и может быть основана либо на URL запроса, либо на заголовках запроса.
Существует несколько правильных подходов к определению версий. Неверсионированные системы также могут быть уместны, особенно если вы разрабатываете очень долгосрочные системы с множеством клиентов вне вашего контроля.
Если версионность API включена, атрибут request.version
будет содержать строку, соответствующую версии, запрошенной во входящем клиентском запросе.
По умолчанию версионность не включена, и request.version
всегда будет возвращать None
.
Как вы будете изменять поведение API, зависит от вас, но один из примеров, который обычно может понадобиться, - это переход на другой стиль сериализации в новой версии. Например:
def get_serializer_class(self):
if self.request.version == 'v1':
return AccountSerializerVersion1
return AccountSerializer
Функция reverse
, включенная в DRF, связана со схемой версионирования. Вам нужно убедиться, что вы включили текущий 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
.
При использовании стилей сериализации с гиперссылками вместе со схемой версионирования на основе 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
Эта схема требует от клиента указать версию как часть типа медиа в заголовке Accept
. Версия включается в качестве параметра медиатипа, дополняющего основной медиатип.
Вот пример HTTP-запроса с использованием стиля версионирования заголовка accept.
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
В приведенном выше примере запроса атрибут request.version
вернет строку '1.0'
.
Версионирование на основе принимаемых заголовков обычно рассматривается как лучшая практика, хотя в зависимости от требований клиента могут подойти и другие стили.
Строго говоря, медиатип json
не указан как включающий дополнительные параметры. Если вы создаете хорошо специфицированный публичный 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
Эта схема требует, чтобы клиент указывал версию как часть пути 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'
)
]
Для клиента эта схема аналогична 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
, вероятно, проще в управлении для больших проектов.
Схема версионности имени хоста требует от клиента указать запрашиваемую версию как часть имени хоста в 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
. Существуют различные онлайн-уроки о том, как получить доступ к localhost с пользовательским поддоменом, которые могут оказаться полезными в этом случае.
Версионность на основе имени хоста может быть особенно полезна, если у вас есть требования направлять входящие запросы на разные серверы в зависимости от версии, поскольку вы можете настроить разные записи DNS для разных версий API.
Эта схема представляет собой простой стиль, который включает версию в качестве параметра запроса в 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()
в классе. Примеры см. в исходном коде.