码农行者 码农行者
首页
  • Python

    • 语言特性
    • Django相关
    • Tornado
    • Celery
  • Golang

    • golang学习笔记
    • 对比python学习go
    • 模块学习
  • JavaScript

    • Javascript
  • 数据结构预算法笔记
  • ATS
  • Mongodb
  • Git
云原生
运维
垃圾佬的快乐
  • 数据库
  • 机器学习
  • 杂谈
  • 面试
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

DeanWu

软件工程师
首页
  • Python

    • 语言特性
    • Django相关
    • Tornado
    • Celery
  • Golang

    • golang学习笔记
    • 对比python学习go
    • 模块学习
  • JavaScript

    • Javascript
  • 数据结构预算法笔记
  • ATS
  • Mongodb
  • Git
云原生
运维
垃圾佬的快乐
  • 数据库
  • 机器学习
  • 杂谈
  • 面试
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python

    • 语言特性

    • Django

    • Best.Practices.for.Django

    • Djangorestfulframework

      • Django restful framework 文档
      • Django restful framework 学习使用总结-权限相关
      • Django restful framework 使用笔记 -- 踩坑记录
      • 「译」Django restful framework 中API版本的管理
        • 什么情况下会有多版本的 api 的需求
        • DRF 中支持的版本管理方案
          • AcceptHeaderVersioning
          • URLPathVersioning
          • NamespaceVersioning
          • HostNameVersioning
          • QueryParameterVersioning
        • DRF 中版本化代码
        • 我们是如何处理版本的
        • 以上方法的优缺点
          • 优点
          • 缺点
        • 总结
          • 参考:
      • 「译」Django restful framework 性能优化
    • Celery

    • Tornado

    • Flask

    • FastApi

    • virtualenv

  • Golang

  • Javascript

  • 开发语言
  • Python
  • Djangorestfulframework
DeanWu
2017-04-24
目录

「译」Django restful framework 中API版本的管理

原文:https://gearheart.io/blog/api-versioning-with-django-rest-framework/

# 什么情况下会有多版本的 api 的需求

我们在升级服务的时候,通常是向后兼容的。这样我们在升级客户端代码的时候,便不会遇到太大的困难。然而,当移动端的api升级后,客户手机中的app客户端有可能不会升级,所以我们必须保证所有版本的API的正常运行。

一个系统应该有一个好的api版本控制:新的功能和更改应该在新的版本中。旧的客户端可以使用旧的API,新的客户端可以使用新版本的API。

# DRF 中支持的版本管理方案

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

# AcceptHeaderVersioning

通过接受请求标头传递版本号:

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
1
2
3

# URLPathVersioning

将版本以变量的方式添加到url地址(通过VERSION_PARAM参数在DRF中指定路径):

urlpatterns = [
    url(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    )
]
1
2
3
4
5
6
7

# NamespaceVersioning

通过 url namespace 来区分版本:

# urls.py
urlpatterns = [
    url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
    url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]
1
2
3
4
5

# HostNameVersioning

通过域名来设置版本:

http://v1.example.com/bookings/
http://v2.example.com/bookings/
1
2

# QueryParameterVersioning

通过 get query string 参数来专递版本:

http://example.com/bookings/?version=0.1
http://example.com/bookings/?version=0.2
1
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
1
2
3
4
5
6
7
8
9

如果我们需要更改/删除/添加一个字段,我们创建一个新的序列化程序并更改其中的字段。

class AccountSerializerVersion1(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created', 'updated')
1
2
3
4

然后我们在AccountViewSet中重新定义get_serializer_class方法:

def get_serializer_class(self):
    if self.request.version == 'v1':
        return AccountSerializerVersion1
    return AccountSerializer
1
2
3
4

这是在ViewSet中重新定义序列化程序,权限类和其他方法的一种方法。

同时,我发现一个小的版本控制的项目 (opens new window)。

我没有使用它,但是从文档中我们可以设置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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

设置基本版本:

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',
        )
1
2
3
4
5
6
7
8
9
10
11
12
13

我们这样创建每个新版本:

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
1
2
3
4
5
6
7
8
9
10
11
12

从客户端接收数据(即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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

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'
1
2
3
4
5
6
7
8
9
10
11
12
# 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
1
2
3
4
5
6
7
8
9
10
11
# 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
1
2
3
4
5
6
7
8
9

此外,我们将urls.py连接到第一个API版本:

from .api.base.router import api_urlpatterns as api_v1

urlpatterns = [
    url(r'^api/v1/', include(api_v1)),
]
1
2
3
4
5

我们删除了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
1
2
3
4
5
6
7

继承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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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
1
2
3
4
5
6
7
8
# 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
1
2
3
4
5
6
7
8

更新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)),
]
1
2
3
4
5
6
7

您可能会注意到我们已经继承了UserViewSet,而且我们没有更新BookViewSet,这是因为我们在v2视图视图中引入了base 的视图。

# 以上方法的优缺点

# 优点

  • 开发简单
  • 相关类的版本由模块基础,v1,v2等分类。
  • 易于浏览代码
  • 无需复制 view 和 serializers 的源代码。
  • 少 if 嵌套

# 缺点

  • 当API 版本过多时,会造成代码继承层数多大,不利于维护。
  • 应为要继承,需要简单修改部分代码。

# 总结

管理API版本可能相当困难,尤其是要正确实施。您可以在每个版本控制方法中找到优缺点。由于我们项目中的版本少,所以继承方法是比较实用的。

# 参考:

  • http://www.django-rest-framework.org/api-guide/versioning/ (opens new window)
  • https://gearheart.io/blog/api-versioning-with-django-rest-framework/ (opens new window)
#Django#Restful#翻译
上次更新: 2023/03/28, 16:27:19
Django restful framework 使用笔记 -- 踩坑记录
「译」Django restful framework 性能优化

← Django restful framework 使用笔记 -- 踩坑记录 「译」Django restful framework 性能优化→

最近更新
01
chromebox/chromebook 刷bios步骤
03-01
02
redis 集群介绍
11-28
03
go语法题二
10-09
更多文章>
Theme by Vdoing | Copyright © 2015-2024 DeanWu | 遵循CC 4.0 BY-SA版权协议
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式