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实现的微信公众号群发图片与文本消息功能实例详解
Jun 30 Python
python 寻找优化使成本函数最小的最优解的方法
Dec 28 Python
numpy添加新的维度:newaxis的方法
Aug 02 Python
python 对给定可迭代集合统计出现频率,并排序的方法
Oct 18 Python
pygame游戏之旅 python和pygame安装教程
Nov 20 Python
python提取具有某种特定字符串的行数据方法
Dec 11 Python
Python+OpenCV图片局部区域像素值处理改进版详解
Jan 23 Python
selenium+python自动化测试之使用webdriver操作浏览器的方法
Jan 23 Python
详解python持久化文件读写
Apr 06 Python
python取均匀不重复的随机数方式
Nov 27 Python
python实现堆排序的实例讲解
Feb 21 Python
Pycharm 如何一键加引号的方法步骤
Feb 05 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
在Yii2中使用Pjax导致Yii2内联脚本载入失败的原因分析
2016/03/06 PHP
PHP接口继承及接口多继承原理与实现方法详解
2017/10/18 PHP
laravel框架使用阿里云短信发送消息操作示例
2020/02/15 PHP
jquery获取焦点和失去焦点事件代码
2013/04/21 Javascript
详解JS函数重载
2014/12/04 Javascript
jQuery事件绑定用法详解(附bind和live的区别)
2016/01/19 Javascript
jQuery简单设置文本框回车事件的方法
2016/08/01 Javascript
JS查找字符串中出现次数最多的字符
2016/09/05 Javascript
js实现固定宽高滑动轮播图效果
2017/01/13 Javascript
Node.js中的不安全跳转如何防御详解
2018/10/21 Javascript
vue中的适配px2rem示例代码
2018/11/19 Javascript
js基础之事件捕获与冒泡原理
2019/10/09 Javascript
微信小程序中使用 async/await的方法实例分析
2020/05/06 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
2020/05/19 jQuery
js实现微信聊天界面
2020/08/09 Javascript
JS实现按比例缩小图片宽高
2020/08/24 Javascript
Vue如何实现变量表达式选择器
2021/02/18 Vue.js
python查询mysql中文乱码问题
2014/11/09 Python
Pandas 合并多个Dataframe(merge,concat)的方法
2018/06/08 Python
获取python的list中含有重复值的index方法
2018/06/27 Python
Pyqt5如何让QMessageBox按钮显示中文示例代码
2019/04/11 Python
django数据模型(Model)的字段类型解析
2019/12/25 Python
python调用私有属性的方法总结
2020/07/24 Python
Python实现哲学家就餐问题实例代码
2020/11/09 Python
Ubuntu权限不足无法创建文件夹解决方案
2020/11/14 Python
用python对oracle进行简单性能测试
2020/12/05 Python
css3制作彩色边线3d立体按钮的示例(css3按钮)
2014/05/06 HTML / CSS
澳大利亚女性快速时尚零售商:Ally Fashion
2018/04/25 全球购物
保险经纪人求职信
2014/03/11 职场文书
做一个有道德的人演讲稿
2014/05/14 职场文书
介绍信怎么写
2015/01/30 职场文书
大学生党员个人总结
2015/02/13 职场文书
个人政治思想总结
2015/03/05 职场文书
python 如何执行控制台命令与操作剪切板
2021/05/20 Python
Go语言的协程上下文的几个方法和用法
2022/04/11 Golang
python和Appium的移动端多设备自动化测试框架
2022/04/26 Python