譯-Django restfull framework 中API版本的管理

原文:gearheart.io/blog/api-ve…javascript

什麼狀況下會有多版本的 api 的需求

咱們在升級服務的時候,一般是向後兼容的。這樣咱們在升級客戶端代碼的時候,便不會遇到太大的困難。然而,當移動端的api升級後,客戶手機中的app客戶端有可能不會升級,因此咱們必須保證全部版本的API的正常運行。java

一個系統應該有一個好的api版本控制:新的功能和更改應該在新的版本中。舊的客戶端可使用舊的API,新的客戶端可使用新版本的API。python

DRF 中支持的版本管理方案

DRF中支持多種版本管理方案。git

AcceptHeaderVersioning

經過接受請求標頭傳遞版本號:github

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

URLPathVersioning

將版本以變量的方式添加到url地址(經過VERSION_PARAM參數在DRF中指定路徑):django

urlpatterns = [
    url(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    )
]複製代碼

NamespaceVersioning

經過 url namespace 來區分版本:json

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

HostNameVersioning

經過域名來設置版本:api

http://v1.example.com/bookings/
http://v2.example.com/bookings/複製代碼

QueryParameterVersioning

經過 get query string 參數來專遞版本:bash

http://example.com/bookings/?version=0.1
http://example.com/bookings/?version=0.2複製代碼

DRF 中版本化代碼

在DRF 文檔中介紹了第一個版本控制的方法。以下:app

建立 Serializer 和 ViewSet

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')


class AccountViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all()
    serializer_class = AccountSerializer複製代碼

若是咱們須要更改/刪除/添加一個字段,咱們建立一個新的序列化程序並更改其中的字段。

class AccountSerializerVersion1(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created', 'updated')複製代碼

而後咱們在AccountViewSet中從新定義get_serializer_class方法:

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

這是在ViewSet中從新定義序列化程序,權限類和其餘方法的一種方法。

同時,我發現一個小的版本控制的項目

我沒有使用它,可是從文檔中咱們能夠設置Serializer和Parser並使用它們來設置變換的基類。

from rest_framework_transforms.transforms import BaseTransform

class TestModelTransform0002(BaseTransform):
    """ Changes between v1 and v2 """
    def forwards(self, data, request):
        if 'test_field_one' in data:
            data['new_test_field'] = data.get('test_field_one')
            data.pop('test_field_one')
        return data

    def backwards(self, data, request, instance):
        data['test_field_one'] = data.get('new_test_field')
        data.pop('new_test_field')
        return data複製代碼

設置基本版本:

class TestSerializerV3(BaseVersioningSerializer):
    transform_base = 'tests.test_transforms.TestModelTransform'

    class Meta:
        model = TestModelV3
        fields = (
            'test_field_two',
            'test_field_three',
            'test_field_four',
            'test_field_five',
            'new_test_field',
            'new_related_object_id_list',
        )複製代碼

咱們這樣建立每一個新版本:

class TestModelTransform0003(BaseTransform):
    """ Changes between v2 and v3 """

    def forwards(self, data, request):
        data['new_related_object_id_list'] = [1, 2, 3, 4, 5]
        return data

    def backwards(self, data, request, instance):
        data.pop('new_related_object_id_list')
        return data複製代碼

從客戶端接收數據(即0004,0003,0002)時,向後的方法將從結尾開始應用。向客戶端發送數據時,轉發將按照0002,0003,0004的順序進行。

咱們是如何處理版本的

基本思想是將API分解爲模塊並使用類繼承。

以下目錄結構:

api/
├── base
│   ├── init.py
│   ├── router.py
│   ├── serializers.py
│   └── views.py
├── init.py
└── versioned
    ├── init.py
    ├── v2
    │   ├── init.py
    │   ├── router.py
    │   ├── serializers.py
    │   └── views.py
    ├── v3
    │   ├── init.py
    │   ├── router.py
    │   ├── serializers.py
    │   └── views.py
    ├── v4
    │   ├── init.py
    │   ├── router.py
    │   ├── serializers.py
    │   └── views.py
    └── v5
        ├── init.py
        ├── router.py
        ├── serializers.py
        └── views.py複製代碼

base - 咱們的基礎版本API,第一個版本。

此外,在版本化文件夾中,咱們爲每一個版本建立了一個文件夾。在這個項目中,咱們有兩個外部客戶:iOS和Android +咱們的WEB客戶端。 WEB客戶端一直使用最新版本的API。

每一個連續的API版本是這樣處理的:咱們在現有的API v2中進行了更改; 在iOS和Android客戶端發佈以後(他們同時發佈),咱們建立了v3,並中止對v2進行更改。

DRF使用類來建立ViewSet,Serializer,Permission。咱們使用API​​版本之間的繼承來徹底複製ViewSets和Serializer。

# base/serializers.py

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'first_name', 'last_name', 'email')


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = 'all'複製代碼
# base/views.py
from . import serializers

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer


class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = serializers.BookSerializer複製代碼
# base/router.py
from . import views


router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'books', views.BookViewSet)

api_urlpatterns = router.urls複製代碼

此外,咱們將urls.py鏈接到第一個API版本:

from .api.base.router import api_urlpatterns as api_v1

urlpatterns = [
    url(r'^api/v1/', include(api_v1)),
]複製代碼

咱們刪除了first_name,last_name字段並添加了full_name字段。而後咱們建立了v2保持向後兼容性,並添加了serializers.py,views.py,router.py目錄和文件:

└── versioned
    ├── init.py
    ├── v2
    │   ├── init.py
    │   ├── router.py
    │   ├── serializers.py
    │   └── views.py複製代碼

繼承base 版本:

# versioned/v2/serializers.py

# import all our basic serializers

from .api.base import serializers as base_serializers
from .api.base.serializers import *

class UserSerializer(base_serializers.UserSerializer):
    full_name = serializers.SerializerMethodField()

    class Meta(base_serializers.UserSerializer.Meta):
        fields = ('id', 'email', 'full_name')

    def get_full_name(self, obj):
        return '{0} {1}'.format(obj.first_name, obj.last_name)複製代碼
# versioned/v2/views.py
from .api.base.views import *
from .api.base import views as base_views
from . import serializers as v2_serializers


class UserViewSet(base_views.UserViewSet):
    serializer_class = v2_serializers.UserSerializer複製代碼
# versioned/v2/router.py
from . import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'books', views.BookViewSet)

api_urlpatterns = router.urls複製代碼

更新root url 文件:

from .api.base.router import api_urlpatterns as api_v1
from .api.versioned.v2.router import api_urlpatterns as api_v2

urlpatterns = [
    url(r'^api/v1/', include(api_v1)),
    url(r'^api/v2/', include(api_v2)),
]複製代碼

您可能會注意到咱們已經繼承了UserViewSet,並且咱們沒有更新BookViewSet,這是由於咱們在v2視圖視圖中引入了base 的視圖。

以上方法的優缺點

優勢

  • 開發簡單
  • 相關類的版本由模塊基礎,v1,v2等分類。
  • 易於瀏覽代碼
  • 無需複製 view 和 serializers 的源代碼。
  • 少 if 嵌套

缺點

  • 當API 版本過多時,會形成代碼繼承層數多大,不利於維護。
  • 應爲要繼承,須要簡單修改部分代碼。

總結

管理API版本可能至關困難,尤爲是要正確實施。您能夠在每一個版本控制方法中找到優缺點。因爲咱們項目中的版本少,因此繼承方法是比較實用的。

參考:

相關文章
相關標籤/搜索