Flask入坑记录(二)整理项目结构
结构规划
上文我们已非常简陋的方式创建了一个Flask的WebApi应用,项目的结构如下图所示。
稍微有点编程经验的同学都会觉得这样的项目结构真的太简陋了,最简单的三层架构里面只有视图层(Controller),业务逻辑层,数据接入层都没有。项目中一点层次感都没有,那么我们应该如何调整项目的结构呢?
项目需要什么
Flask不像ASP.Net Core那样,创建好项目之后,该有的文件夹都替你创建好,项目的层次结构也比较清晰。在Flask里我们就需要自己做这项工作。
规划之前,首先我们要想清楚,一个小型的WebApi应用它需要有什么东西?
- Controller模块(对外接口)
- Model模块(业务逻辑)
- Log模块(日志)
- CORS(跨域)
- JWT(Json Web Token)
- Swagger(API在线文档)
- Config(项目配置文件读写)
业务逻辑层
这里的业务逻辑层其实是一个非常庞大的概念,毕竟项目中的大部分时间我们都在对业务逻辑进行编码。所以这里有必要对其细分一下。
业务逻辑层的细分
其中,Model模块里面还能细分为:
- 数据表实体类模块 db_model
- 数据传输类模块(Data Transfer Object) dto_model
- 提供数据接入层服务的Service模块
- 业务逻辑处理模块 logic_models
- 标准回送及各种错误码等定义
关于业务逻辑层(Model)这里可能会存在争议,有些小伙伴可能会认为数据库相关的东西应该单独抽离出来,或者业务逻辑上还能继续细分,每个开发人员都可能会根据自己的经验有不同的划分意见,这里仅仅是我的个人理解请不要过分深究。我的划分标准就是:只要跟业务逻辑有关系的代码,统统放到Model层,那如何定义”跟业务逻辑有关系”这个界限呢?我的理解是:只要在两个业务逻辑完全不同类型项目里,不能直接复制粘贴使用的都是”跟业务逻辑有关系”的代码。
请求处理流程
根据以上划分的业务逻辑层,我们现在可以先整理一下一个Http请求过来之后的处理流程。
由上图可以看出,用户向Controller发起HTTP请求,Controller不对请求进行任何业务逻辑处理,直接丢给LogicModel(业务逻辑模块)处理,LogicModel负责对请求进行业务逻辑处理,处理时如果需要和数据库打交道,则调用提供数据接入层服务的Service模块,由Service模块统一进行数据库操作,最后返回结果。LogicModel返回给Controller一个标准统一的Result对象,Controller根据Result对象里的错误码(应用自定义的错误码),制作相应的HTTP回送(400,200等)返回给用户。
使用Flask的插件
正如上篇文章所提到的,Flask是一个插件非常丰富的框架,我们在上节中规划好的项目结构,大部分都能找到现成的插件。以下就是我们需要用到的插件整理。
插件名 | 用途 |
---|---|
flask_jwt_extended | 提供JWT验证 |
flasgger | 提供Swagger的WebApi在线文档 |
flask_cors | CORS跨域 |
flask_sqlalchemy | 数据库ORM框架 |
如果需要连接MySQL数据库的话,还需要用到
pymysql
这个库作为Connector。
插件的安装
插件的安装和正常的Python包安装是一样的。
Windows下
pip install {插件名}
Linux下
pip3 install {插件名}
插件的初始化
我们安装完插件包之后,下一步就是要在项目中调用了。Flask常用的插件一般都会有两种初始化的方式:
- 通过构造函数传入Flask app对象初始化。
- 插件对象实例化之后通过init_app(Flask: app)方法初始化。
这里推荐使用第二种方法进行初始化,绝大部分的Flask插件都会有init_app这个方法。
整理结构
接下来就可以开始进入正题了,我们应该如何整理Flask项目的结构?在此之前还是要多费一点时间了解一下Python的模块,因为后续我们的项目大部分都是以模块为最小组成部分。
Python的模块
Python中的模块可以是一个py文件,也可以是一个文件夹。可以把Python的模块类比成C#里的命名空间,只要文件夹中有__init__.py这个文件,那么这个文件夹就是一个模块。有需要的小伙伴可以参考以下连接。
Python中的模块
消除循环依赖
有了模块的概念之后,细心的小伙伴可能会发现,Flask官方的Demo中引用其他插件其实是存在循环依赖的。Flask app需要引用其插件,而插件的初始化又需要引用Flask app,这就是一个循环依赖。
针对这种情况,我们可以把项目的结构设计成这样:
首先,项目应该有一个统一启动入口,在入口处我们创建Flask app实例,再将这个实例传到各个插件进行初始化。
DemoApp模块就是我们的Flask Api应用模块
DemoApp的__init__.py
# -*- coding: utf-8 -*-
from flask import Flask
from flask_cors import CORS
from .models import db_models
from . import log
from . import swagger
from . import jwt
def create_app():
# 生成WebApp
app = Flask(__name__)
# 跨域
CORS(app, supports_credentials=True)
# 初始化数据库映射和链接
db_models.init(app)
# 初始化日志组件
log.init()
# 注册Controller
__register_blueprint__(app)
# 初始化jwt
jwt.init(app)
# 初始化Swagger
swagger.init(app)
return app
def __register_blueprint__(app: Flask):
# 引用Controlls里面的蓝图并注册
from .controllers.DemoController import route_demo
app.register_blueprint(route_demo, url_prefix='/api')
注意这里引用的log、swagger、jwt、db_models模块都是对相应Flask插件的简单封装。以jwt为例。
自己封装的jwt模块中的__init__.py
# -*- coding: utf-8 -*-
from flask_jwt_extended import JWTManager
from flask import Flask
# 这个是插件的jwt实例
jwt = JWTManager()
def init(app: Flask):
# JWT加密密钥
app.config['JWT_SECRET_KEY'] = 'my-sectet'
jwt.init_app(app)
总入口: run.py
# -*- coding: utf-8 -*-
import DemoApp
# 创建应用
app = DemoApp.create_app()
def main():
# 运行在8848端口上并接受所有地址的请求
app.run(host='0.0.0.0', port=8848)
if __name__ == '__main__':
main()
到此为止,我们项目依赖关系如下: run.py依赖于DemoApp模块,DemoApp模块依赖于多个简单封装的Flask插件模块和若干业务逻辑模块。
项目结构概览
|-- run.py // 总入口
|-- DemoApp // WebApi程序
|-- controllers // Controller模块
|-- DomoController.py
|-- ...
|-- __init__.py
|-- models // 业务逻辑模块
|-- db_models // 数据库实体类模块
|-- dto_models // 数据传输实体模块
|-- ...
|-- __init__.py
|-- jwt // JWT插件模块
|-- log // 日志模块
|-- swagger // Swagger在线Api文档模块
|-- config // 程序相关配置模块
|-- __init__.py
完整源码
下节内容
下一节将针对以上提到的Flask插件进行踩坑记录。