結構規劃
上文我們已非常簡陋的方式創建了一個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
DemoApp的Github地址會有的,待我整理一番再放上來。
下節內容
下一節將針對以上提到的Flask插件進行踩坑記錄。