Django REST framework API 指南(18):版本控制

wcode發表於2019-02-24

官方原文連結
本系列文章 github 地址
轉載請註明出處

版本控制

API 版本控制允許你更改不同客戶端之間的行為。 REST framework 提供了許多不同的版本控制方案。

版本控制由傳入的客戶端請求決定,可能基於請求 URL 或請求 header。

有幾種有效的方法來處理版本控制。非版本化的系統也可能是合適的,特別是如果你正在為超出控制範圍的多個客戶端的非常長期的系統進行工程設計。

使用 REST framework 進行版本控制

當啟用 API 版本控制時, request.version 屬性將包含一個對應於傳入客戶端請求的版本的字串。

預設情況下,版本控制未啟用,request.version 將始終返回 None

基於版本的變化行為

如何改變 API 的行為取決於你,但你可能通常需要的一個示例是在新版本中切換到不同的序列化樣式。例如:

def get_serializer_class(self):
    if self.request.version == `v1`:
        return AccountSerializerVersion1
    return AccountSerializer
複製程式碼

反向解析版本化 API 的 URL

REST framework 包含的 reverse 函式與版本控制方案相關聯。你需要確保將當前請求包含為關鍵字引數,如下所示。

from rest_framework.reverse import reverse

reverse(`bookings-list`, request=request)
複製程式碼

上述功能將應用適合請求版本的任何 URL 轉換。例如:

  • 如果正在使用 NamespacedVersioning,並且 API 版本為 `v1`,則使用的 URL lookup 將為 `v1:bookings-list`,可能會解析為像 http://example.org/v1/bookings/ 這樣的 URL。
  • 如果正在使用 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 setting key 定義。

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
複製程式碼

其他版本設定

以下 settings key 也用於控制版本控制:

  • DEFAULT_VERSION. 當沒有版本資訊存在時,用於 request.version 的值。預設為 None
  • ALLOWED_VERSIONS. 如果設定,則此值將限制版本控制方案可能返回的版本集,如果提供的版本不在此集中,則會引發錯誤。請注意,用於 DEFAULT_VERSION 設定的值始終被認為是 ALLOWED_VERSIONS 集的一部分(除非它是 None)。預設為 None
  • VERSION_PARAM. 應該用於任何版本控制引數的字串,例如媒體型別或 URL 查詢引數。預設為 `version`

你還可以通過定義自己的版本控制方案並使用 default_versionallowed_versionsversion_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 header 中媒體型別的一部分。該版本作為媒體型別引數包含在內,它補充了主要媒體型別。

這是一個使用 accept header 版本風格的示例 HTTP 請求。

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
複製程式碼

在上面的示例請求中 request.version 屬性將返回字串 `1.0`

基於 Accept header 的版本控制通常被認為是最佳實踐,儘管其他樣式可能更適合你的客戶端需求。

使用 accept header 和 vendor media type

嚴格地說,json media type 不能被指定為包含附加引數。如果你正在構建精心指定的公共 API,則可以考慮使用vendor media type。為此,請將你的渲染器配置為使用基於 JSON 的渲染器和自定義 media type:

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 = [
    url(
        r`^(?P<version>(v1|v2))/bookings/$`,
        bookings_list,
        name=`bookings-list`
    ),
    url(
        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 = [
    url(r`^$`, bookings_list, name=`bookings-list`),
    url(r`^(?P<pk>[0-9]+)/$`, bookings_detail, name=`bookings-detail`)
]

# urls.py
urlpatterns = [
    url(r`^v1/bookings/`, include(`bookings.urls`, namespace=`v1`)),
    url(r`^v2/bookings/`, include(`bookings.urls`, namespace=`v2`))
]
複製程式碼

如果你只需要簡單的版本控制方案,那麼 URLPathVersioningNamespaceVersioning 都是可以的。 URLPathVersioning方法可能更適合於小型專案,NamespaceVersioning 可能更容易管理大型專案。

HostNameVersioning

hostname 版本控制方案要求客戶端將請求的版本指定為 URL 中 hostname 的一部分。

例如,以下是對 http://v1.example.com/bookings/ URL 的 HTTP 請求:

GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json 
複製程式碼

預設情況下,這個實現期望 hostname 匹配這個簡單的正規表示式:

^([a-zA-Z0-9]+).[a-zA-Z0-9]+.[a-zA-Z0-9]+$
複製程式碼

請注意,第一組用括號括起來,表示這是 hostname 的匹配部分。

HostNameVersioning 方案在除錯模式下可能會很笨拙,因為你通常會訪問諸如 127.0.0.1 的原始 IP 地址。

如果你有要求根據版本將傳入請求路由到不同的伺服器,那麼基於 hostname 的版本控制就會特別有用,因為你可以為不同的 API 版本配置不同的 DNS 記錄。

QueryParameterVersioning

該方案是一種簡單的風格,其中包含版本作為 URL 中的查詢引數。例如:

GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json 
複製程式碼

自定義版本控制方案

要實現自定義版本控制方案,請繼承 BaseVersioning並覆蓋 .determine_version 方法。

舉個例子

以下示例使用自定義的 X-API-Version header 來確定所請求的版本。

class XAPIVersionScheme(versioning.BaseVersioning):
    def determine_version(self, request, *args, **kwargs):
        return request.META.get(`HTTP_X_API_VERSION`, None)
複製程式碼

如果你的版本控制方案基於請求 URL,則還需要更改版本化 URL 的確定方式。為了做到這一點,你應該重寫類的 .reverse()方法。有關示例,請參閱原始碼。

相關文章