阿里云服务器ECS    
弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新 [咨询更多]
阿里云存储OSS
简单易用、多重冗余、数据备份高可靠、多层次安全防护安全性更强、低成本 [咨询更多]
阿里云数据库RDS
稳定可靠、可弹性伸缩、更拥有容灾、备份、恢复、监控、迁移等方面的全套解决方案 [咨询更多]
阿里云安全产品
DDoS高防IP、web应用防火墙、安骑士、sll证书、态势感知众多阿里云安全产品热销中 [咨询更多]
阿里云折扣优惠    
云服务器ECS、数据库、负载均衡等产品新购、续费、升级联系客服获取更多专属折扣 [咨询更多]
DRF 框架之 API 版本管理
2020-7-24    点击量:
     DRF 框架之 API 版本管理   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的兼容:
    ifrequest.version=='v1':
    returnPostSerializerV1()
    returnPostSerializer

    if分支可以视为一段临时代码,我们可以通过适当的方式提醒用户,API已经更改,请尽快迁移到新的版本v2,并且在未来的某个时间,确认大部分用户都成功迁移到新版api后移除掉这些代码,并将默认版本设为v2,这样原本的v1版本的API就彻底被废弃了。
    当然,我们目前的博客接口还暂时没有需要修改升级的地方,不过为了测试API版本管理的设置是否生效了,我们认为添加一个测试用的视图集,在里面做针对不同版本请求的处理,看看不同版本的请求下是否会返回符合预期的不同内容。
    首先在blog/views.py中加一个简单的测试视图集,这个视图集中有个测试用的接口,接口处理逻辑是根据不同的版本号,返回不同的内容:
    classApiVersionTestViewSet(viewsets.ViewSet):
    @action(
    methods=["GET"],detail=False,url_path="test",url_name="test",
    )
    deftest(self,request,*args,**kwargs):
    ifrequest.version=="v1":
    returnResponse(
    data={
    "version":request.version,
    "warning":"该接口的v1版本已废弃,请尽快迁移至v2版本",
    }
    )
    returnResponse(data={"version":request.version})
    当然视图集别忘了在router中注册:
    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/
    HTTP200OK
    Allow:GET,HEAD,OPTIONS
    Content-Type:application/json
    Vary:Accept
    {
    "version":"v1",

    "warning":"该接口的v1版本已废弃,请尽快迁移至v2版本"
    }
    再访问版本号为v2的测试接口,返回的内容就是v2了。
    GET/api/v2/api-version/test/
    HTTP200OK
    Allow:GET,HEAD,OPTIONS
    Content-Type:application/json
    Vary:Accept
    {
    "version":"v2"
    }

    对于其它接口,无论v1,v2版本的接口均可以访问,这样就相当于完成了一次兼容的接口升级。
联系客服免费领取更多阿里云产品新购、续费升级折扣,叠加官网活动折上折更优惠