什麼情況下會有多版本的 api 的需求
我們在升級服務的時候,通常是向後相容的。這樣我們在升級客戶端程式碼的時候,便不會遇到太大的困難。然而,當移動端的api升級後,客戶手機中的app客戶端有可能不會升級,所以我們必須保證所有版本的API的正常執行。
一個系統應該有一個好的api版本控制:新的功能和更改應該在新的版本中。舊的客戶端可以使用舊的API,新的客戶端可以使用新版本的API。
DRF 中支援的版本管理方案
DRF中支援多種版本管理方案。
AcceptHeaderVersioning
通過接受請求標頭傳遞版本號:
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0複製程式碼
URLPathVersioning
將版本以變數的方式新增到url地址(通過VERSION_PARAM引數在DRF中指定路徑):
urlpatterns = [
url(
r'^(?P<version>(v1|v2))/bookings/$',
bookings_list,
name='bookings-list'
)
]複製程式碼
NamespaceVersioning
通過 url namespace 來區分版本:
# urls.py
urlpatterns = [
url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]複製程式碼
HostNameVersioning
通過域名來設定版本:
http://v1.example.com/bookings/
http://v2.example.com/bookings/複製程式碼
QueryParameterVersioning
通過 get query string 引數來專遞版本:
http://example.com/bookings/?version=0.1
http://example.com/bookings/?version=0.2複製程式碼
DRF 中版本化程式碼
在DRF 文件中介紹了第一個版本控制的方法。如下:
建立 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版本可能相當困難,尤其是要正確實施。您可以在每個版本控制方法中找到優缺點。由於我們專案中的版本少,所以繼承方法是比較實用的。