在Flask中支持多个API版本

69

我开始使用Flask和Python设计RESTful webservice,并想知道如何在同一个项目中支持多个API版本。

我考虑将请求的API版本放在URL中,像这样:

/myapp/v1/Users

经过一段时间后,我想在 API 的 1.1 版本中添加另一个端点,并保留 v1 中未更改的所有内容:

/myapp/v1.1/Users   <= Same as in v1
/myapp/v1.1/Books

在v2中,“Users”端点已更改:

/myapp/v2/Users   <= Changed in v2
/myapp/v2/Books   <= Same as in v1.1

看着这个问题,最简单的方法可能是这样的:

and so on...

@app.route('/<version>/users')
def users(version):
    # do something
    return jsonify(response)

但我可以想象,随着每个新的 API 版本发布,这将越来越难以维护。因此,我想知道是否有更好(更易于维护和更好的结构化)的方法在 Flask 中实现这一目标?

2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
125

我是您所提及问题的被采纳答案的作者。我认为像您说的那样使用/<version>/users 方法并不是非常有效。如果你必须管理三个或四个不同版本,最终会得到混乱的代码。

我在那里提出的nginx方案更好,但缺点是必须托管两个独立的应用程序。当时我错过了第三种选择,即为每个API版本使用一个蓝图。例如,请考虑以下应用程序结构(为清晰起见大大简化):

my_project
+-- api/
    +-- v1/
        +-- __init__.py
        +-- routes.py
    +-- v1_1/
        +-- __init__.py
        +-- routes.py
    +-- v2/
        +-- __init__.py
        +-- routes.py
    +-- __init__.py
    +-- common.py

这里有一个 api/common.py,它实现了所有版本API所需的公共功能。例如,您可以拥有一个辅助函数(未装饰为路由),响应于您的/users路由,并在v1和v1.1中完全相同。

每个API版本的routes.py定义了路由,必要时调用common.py函数以避免重复逻辑。例如,您的v1和v1.1 routes.py 可能包含如下内容:

from api import common

@api.route('/users')
def get_users():
    return common.get_users()

请注意api.route。这里的api是蓝图。将每个API版本实现为蓝图有助于将所有内容与正确版本的URL相结合。下面是一个示例应用设置代码,将API蓝图导入到应用程序实例中:

from api.v1 import api as api_v1
from api.v1_1 import api as api_v1_1
from api.v2 import api as api_v2

app.register_blueprint(api_v1, url_prefix='/v1')
app.register_blueprint(api_v1_1, url_prefix='/v1.1')
app.register_blueprint(api_v2, url_prefix='/v2')

这种结构非常好,因为它将所有API版本分开,但它们由同一个应用程序提供。作为附加好处,当停止支持v1时,只需删除该版本的register_blueprint调用,从源中删除v1包,就完成了。

现在,我们需要尽力以一种最小化必须更新版本的风险的方式设计API。请注意,添加新路由不需要新的API版本,使用新路由扩展API是完全可以的。而对现有路由的更改有时可以设计成不影响旧客户端的方式。有时更新API并具有更多自由更改事物会更容易,但理想情况下,这种情况不应经常发生。


2
@miguel 谢谢。如果您不介意,我有几个问题。我的理解正确吗?假设我有一个API的初始版本(v1)。然后我想要v2。我复制整个v1目录并将其命名为v2。然后我可以在v2目录的__init__.py中设置蓝图,并按照您最初的答案在主应用程序中注册它。我想我的担忧是我们仍然会得到多个代码版本。如果一组端点从v1到v2没有更改,则该代码将在两个位置重复出现?所有版本都需要应用修复。蓝图的好处是什么?结构? - thecountofzero
2
@thecountofzero 首先,尽可能避免使用多个版本。这应该是最后的措施,当您进行完全或接近完全的API改进时。对于较小的更改,请设计您的API以便它可以演变。但是,如果您需要在多个API版本上公开某些端点,则从共享模块或包导入这些端点绝对没有问题。 - Miguel Grinberg
1
@miguel,那么当您拥有完全开发的API(v1)并希望添加新的“破坏性”功能(例如更改响应输出格式)时,您的策略是什么?同时,您想添加新的端点,但不希望当前v1客户端可用。使用共享模块可以解决问题,但一旦一个版本需要更改,就会影响其他版本。那么,您是否将“更改”的副本移动到单独的vX目录中? - thecountofzero
2
@thecountofzero,我看到你的情况存在一些问题。如果你需要不同的响应格式,只需使用不同的内容类型(可能是vnd类型),无需提高版本号。添加端点并不希望v1客户端看到对我来说毫无意义。如果您想防止某些客户端获取资源,只需返回错误代码。也许403适用于此? - Miguel Grinberg
2
是的,但如果我没记错的话,我的假设是这个示例中蓝图是在__init__.py文件中创建的。 - Miguel Grinberg
显示剩余13条评论

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,