DRF框架API版本管理实现方法解析


Posted in Python onAugust 21, 2020

API 不可能一成不变,无论是新增或者删除已有 API,都会对调用它的客户端产生影响。如果对 API 的增删没有管理,随着 API 的增增减减,调用它的客户端就会逐渐陷入迷茫,到底哪个 API 是可用的?为什么之前可用的 API 又不可用了,新增了哪些 API 可以使用?为了方便 API 的管理,我们引入版本功能。

给 API 打上版本号,在某个特定版本下,原来已有的 API 总是可用的。如果要对 API 做重大变更,可以发布一个新版本的 API,并及时提醒用户 API 已变更,敦促用户迁移到新的 API,这样可以给客户端提供一个缓冲过渡期,不至于昨天能用的 API,今天突然报错了。

django-rest-framework 提供了多个 API 版本辅助类,分别实现不同的 API 版本管理方式。比较实用的有:

AcceptHeaderVersioning

这个类要求客户端在 HTTP 的 Accept 请求头加上版本号以表明想请求的 API 版本,例如如下请求:

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

这将请求版本号为 1.0 的接口。

URLPathVersioning

这个类要求客户端在请求的 url 中指定版本号,一个缺点是你在书写 URL 模式时,必须包含关键字为 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 类似,只不过版本号不是在 URL 模式中指定,而是通过 namespace 参数指定 (稍后我们将看到它的具体用法)。

当然,django-rest-framework 还提供了其它诸如 HostNameVersioning、QueryParameterVersioning 的版本管理辅助类,可自行查看文档了解:https://www.django-rest-framework.org/api-guide/versioning/

综合来看,NamespaceVersioning 模式便于 URL 的设计与管理,因此我们的博客应用决定采用这种 API 版本管理方式。

为了开启 api 版本管理,在项目的配置中加入如下配置:

settings/common.py
REST_FRAMEWORK = {
  'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
  'DEFAULT_VERSION': 'v1'
}

以上两项设置分别全局指定使用的 API 版本管理方式和客户端缺省版本号的情况下默认请求的 API 版本。尽管这些配置项也可以在单个视图或者视图集的范围内指定,但是,统一的版本管理模式更为可取,因此我们在全局配置中指定。

接着在注册的 API 接口前带上版本号:

blogproject/urls.py
urlpatterns = [
  # ...
  path("api/v1/", include((router.urls, "api"), namespace="v1")),
]

注意这里比之前多了个 namespace 参数,参数值为 v1,代表包含的 URL 模式均属于 v1 这个命名空间。还有一点需要注意,对于 include 函数,如果指定了 namespace 的值,第一个参数必须是一个元组,形式为:(url_patterns, app_name),这里我们将 app_name 指定为 api。

一旦我们开启了版本管理,所有请求对象 request 就会多出一个属性 version,其值为用户请求的版本号(如果没有指定,就为默认的 DEFAULT_VERSION 的值)。因此,我们可以在请求中针对不同版本的请求执行不同的代码逻辑。比如我们的博客修改文章列表 API,序列化器对返回数据的字段做了一些改动,发布在版本 v2,那么可以根据用户用户请求的版本,返回不同的数据,即新增了 API,又保持对原 api 的兼容:

if request.version == 'v1':
	return PostSerializerV1()
return PostSerializer

if 分支可以视为一段临时代码,我们可以通过适当的方式提醒用户,API 已经更改,请尽快迁移到新的版本 v2,并且在未来的某个时间,确认大部分用户都成功迁移到新版api后移除掉这些代码,并将默认版本设为v2,这样原本的 v1 版本的 API 就彻底被废弃了。

当然,我们目前的博客接口还暂时没有需要修改升级的地方,不过为了测试 API 版本管理的设置是否生效了,我们认为添加一个测试用的视图集,在里面做针对不同版本请求的处理,看看不同版本的请求下是否会返回符合预期的不同内容。

首先在 blog/views.py 中加一个简单的测试视图集,这个视图集中有个测试用的接口,接口处理逻辑是根据不同的版本号,返回不同的内容:

class ApiVersionTestViewSet(viewsets.ViewSet):
  @action(
    methods=["GET"], detail=False, url_path="test", url_name="test",
  )
  def test(self, request, *args, **kwargs):
    if request.version == "v1":
      return Response(
        data={
          "version": request.version,
          "warning": "该接口的 v1 版本已废弃,请尽快迁移至 v2 版本",
        }
      )
    return Response(data={"version": request.version})

当然视图集别忘了在 router 中注册:

blogproject/urls.py

blogproject/urls.py

# 仅用于 API 版本管理测试
router.register(
  r"api-version", blog.views.ApiVersionTestViewSet, basename="api-version"
)

这相当于一次接口版本升级,我们再加入 v2 命名空间的接口:

urlpatterns = [
  path("api/v1/", include((router.urls, "api"), namespace="v1")),
  path("api/v2/", include((router.urls, "api"), namespace="v2")),
]

可以看到,包含的 URL 都是一样的,只是 namespace 是 v2。

来测试一下效果,启动开发服务器,先访问版本号为 v1 的测试接口,请求返回结果如下,可以看到如期返回了 v1 版本下的内容:

GET /api/v1/api-version/test/
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
  "version": "v1",
  "warning": "该接口的 v1 版本已废弃,请尽快迁移至 v2 版本"
}

再访问版本号为 v2 的测试接口,返回的内容就是 v2 了。

GET /api/v2/api-version/test/

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
  "version": "v2"
}

对于其它接口,无论 v1,v2 版本的接口均可以访问,这样就相当于完成了一次兼容的接口升级。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python网络编程学习笔记(二):socket建立网络客户端
Jun 09 Python
使用graphics.py实现2048小游戏
Mar 10 Python
Python中矩阵库Numpy基本操作详解
Nov 21 Python
基于Django contrib Comments 评论模块(详解)
Dec 08 Python
Appium+Python自动化测试之运行App程序示例
Jan 23 Python
Opencv-Python图像透视变换cv2.warpPerspective的示例
Apr 11 Python
Django 多表关联 存储 使用方法详解 ManyToManyField save
Aug 09 Python
python 消除 futureWarning问题的解决
Dec 25 Python
在tensorflow中设置保存checkpoint的最大数量实例
Jan 21 Python
Selenium之模拟登录铁路12306的示例代码
Jul 31 Python
快速创建python 虚拟环境
Nov 28 Python
python opencv常用图形绘制方法(线段、矩形、圆形、椭圆、文本)
Apr 12 Python
Django rest framework分页接口实现原理解析
Aug 21 #Python
Python -m参数原理及使用方法解析
Aug 21 #Python
python使用布隆过滤器的实现示例
Aug 20 #Python
QT5 Designer 打不开的问题及解决方法
Aug 20 #Python
Python配置pip国内镜像源的实现
Aug 20 #Python
Python使用lambda抛出异常实现方法解析
Aug 20 #Python
浅谈对python中if、elif、else的误解
Aug 20 #Python
You might like
PHP的反射类ReflectionClass、ReflectionMethod使用实例
2014/08/05 PHP
[原创]php简单隔行变色功能实现代码
2016/07/09 PHP
php使用flock阻塞写入文件和非阻塞写入文件的实例讲解
2017/07/10 PHP
jquery禁用右键单击功能屏蔽F5刷新
2014/03/17 Javascript
js监听鼠标点击和键盘点击事件并自动跳转页面
2014/09/24 Javascript
iframe里使用JavaScript控制主页转向的方法
2015/04/03 Javascript
PHP和NodeJs开发的应用如何共用Session
2015/04/16 NodeJs
JS+CSS实现DIV层的展开、收缩效果
2016/01/28 Javascript
AngularJS HTML DOM详解及示例代码
2016/08/17 Javascript
html5+CSS 实现禁止IOS长按复制粘贴功能
2016/12/28 Javascript
微信小程序 图片宽高自适应详解
2017/05/11 Javascript
vue2.0 datepicker使用方法
2018/02/04 Javascript
elementUI select组件value值注意事项详解
2019/05/29 Javascript
如何在 Vue 中使用 JSX
2021/02/14 Vue.js
[03:48]2014DOTA2 TI专访71DK夺冠不靠小组赛高排名
2014/07/11 DOTA
Python中的文件和目录操作实现代码
2011/03/13 Python
django批量导入xml数据
2016/10/16 Python
Django自定义分页效果
2017/06/27 Python
Python3中内置类型bytes和str用法及byte和string之间各种编码转换 问题
2018/09/27 Python
python使用PIL实现多张图片垂直合并
2019/01/15 Python
PyQt5实现类似别踩白块游戏
2019/01/24 Python
python3 selenium自动化 frame表单嵌套的切换方法
2019/08/23 Python
python 判断txt每行内容中是否包含子串并重新写入保存的实例
2020/03/12 Python
结束运行python的方法
2020/06/16 Python
Python性能分析工具py-spy原理用法解析
2020/07/27 Python
印度尼西亚最大和最全面的网络商城:Blibli.com
2017/10/04 全球购物
英国时尚优质的女装:Hope Fashion
2018/08/14 全球购物
世界汽车零件:World Car Parts
2019/09/04 全球购物
展会邀请函范文
2014/01/26 职场文书
运动会广播稿20字
2014/02/18 职场文书
《大作家的小老师》教学反思
2014/04/16 职场文书
汉语专业毕业生自荐信
2014/07/06 职场文书
大学生党员学习焦裕禄精神思想汇报
2014/09/10 职场文书
2014公司党员自我评价范文
2014/09/11 职场文书
县政府领导班子四风问题对照检查材料思想汇报
2014/09/26 职场文书
MyBatis 动态SQL全面详解
2021/10/05 MySQL