原文:gearheart.io/blog/api-ve…javascript
咱们在升级服务的时候,一般是向后兼容的。这样咱们在升级客户端代码的时候,便不会遇到太大的困难。然而,当移动端的api升级后,客户手机中的app客户端有可能不会升级,因此咱们必须保证全部版本的API的正常运行。java
一个系统应该有一个好的api版本控制:新的功能和更改应该在新的版本中。旧的客户端可使用旧的API,新的客户端可使用新版本的API。python
DRF中支持多种版本管理方案。git
经过接受请求标头传递版本号:github
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0复制代码
将版本以变量的方式添加到url地址(经过VERSION_PARAM参数在DRF中指定路径):django
urlpatterns = [
url(
r'^(?P<version>(v1|v2))/bookings/$',
bookings_list,
name='bookings-list'
)
]复制代码
经过 url namespace 来区分版本:json
# urls.py
urlpatterns = [
url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]复制代码
经过域名来设置版本:api
http://v1.example.com/bookings/
http://v2.example.com/bookings/复制代码
经过 get query string 参数来专递版本:bash
http://example.com/bookings/?version=0.1
http://example.com/bookings/?version=0.2复制代码
在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 的视图。
管理API版本可能至关困难,尤为是要正确实施。您能够在每一个版本控制方法中找到优缺点。因为咱们项目中的版本少,因此继承方法是比较实用的。