本文 AI 產出,尚未審核

FastAPI 教學 – 應用結構與啟動方式:Application Factory Pattern


簡介

在大型或可擴充的 FastAPI 專案中,直接在 main.py 中寫死所有路由、設定與中介軟體,往往會造成維護成本高、測試困難的問題。
Application Factory Pattern(應用工廠模式)提供了一種「延遲建立」FastAPI 物件的方式,讓我們可以在不同情境(開發、測試、部署)下,使用相同的程式碼基礎,卻得到不同的設定與相依性。

透過工廠函式,我們可以:

  • 分離設定與路由,讓程式碼結構更清晰。
  • 在測試時輕鬆產生獨立的 app 實例,避免測試互相干擾。
  • 支援多種啟動方式(如 uvicorn main:app、Docker、Gunicorn),只要呼叫同一個工廠即可。

以下將一步一步說明這個模式的核心概念與實作細節,並提供實用範例、常見陷阱與最佳實踐,幫助你在專案中快速導入。


核心概念

1. 什麼是 Application Factory

Application Factory 本質上是一個返回 FastAPI 實例的函式
它不會在模組載入時直接建立 FastAPI(),而是等到程式需要時才呼叫,讓我們可以在建立過程中注入設定、註冊路由、加入中介軟體等。

def create_app(config_object: str = "app.config.DevConfig") -> FastAPI:
    app = FastAPI()
    # 讀取設定
    app.state.config = import_string(config_object)()
    # 註冊路由、事件、middleware...
    return app

重點:工廠函式接受參數(如設定檔路徑),使同一套程式碼可以在不同環境下產生不同的 app

2. 專案目錄建議

myproject/
├─ app/
│  ├─ __init__.py          # 只放 create_app
│  ├─ config.py           # 各環境設定類別
│  ├─ api/
│  │   ├─ __init__.py
│  │   └─ v1.py           # 路由模組
│  ├─ core/
│  │   ├─ __init__.py
│  │   └─ middleware.py   # 中介軟體
│  └─ models/             # ORM / Pydantic
├─ tests/
│  └─ test_api.py
├─ main.py                 # 只負責呼叫 create_app
└─ requirements.txt

3. 實作步驟

3.1 建立設定類別

# app/config.py
class BaseConfig:
    TITLE = "My FastAPI Service"
    DEBUG = False

class DevConfig(BaseConfig):
    DEBUG = True
    LOG_LEVEL = "debug"

class ProdConfig(BaseConfig):
    LOG_LEVEL = "info"

3.2 撰寫工廠函式

# app/__init__.py
from fastapi import FastAPI
from importlib import import_module
from .config import BaseConfig

def create_app(config_class: str = "app.config.DevConfig") -> FastAPI:
    """Application factory for FastAPI."""
    # 動態匯入設定類別
    module_path, class_name = config_class.rsplit(".", 1)
    config = getattr(import_module(module_path), class_name)()
    
    app = FastAPI(
        title=config.TITLE,
        debug=config.DEBUG,
    )
    app.state.config = config   # 讓其他模組可以存取設定

    # 1️⃣ 註冊中介軟體
    from .core.middleware import add_middlewares
    add_middlewares(app)

    # 2️⃣ 註冊路由
    from .api import v1
    app.include_router(v1.router, prefix="/api/v1")

    # 3️⃣ 設定啟動與關閉事件
    @app.on_event("startup")
    async def on_startup():
        # 例如建立資料庫連線
        pass

    @app.on_event("shutdown")
    async def on_shutdown():
        # 關閉連線
        pass

    return app

3.3 路由模組化

# app/api/v1.py
from fastapi import APIRouter, Depends

router = APIRouter()

def get_current_user():
    # 假設的依賴項
    return {"username": "demo"}

@router.get("/ping")
async def ping():
    """最簡單的健康檢查端點"""
    return {"msg": "pong"}

@router.get("/users/me")
async def read_me(user: dict = Depends(get_current_user)):
    """示範使用 Depends 注入"""
    return {"user": user}

3.4 中介軟體範例

# app/core/middleware.py
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

def add_middlewares(app: FastAPI) -> None:
    """在 app 中加入跨域與自訂中介軟體"""
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    # 你可以在此加入日誌、驗證等自訂中介軟體

3.5 入口檔案(main.py)

# main.py
import uvicorn
from app import create_app

app = create_app("app.config.DevConfig")   # 依需求切換設定

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

4. 測試時的應用

使用 TestClient,只要呼叫工廠即可得到一個 乾淨、獨立 的 app 實例。

# tests/test_api.py
from fastapi.testclient import TestClient
from app import create_app

def test_ping():
    app = create_app("app.config.DevConfig")
    client = TestClient(app)
    response = client.get("/api/v1/ping")
    assert response.status_code == 200
    assert response.json() == {"msg": "pong"}

常見陷阱與最佳實踐

陷阱 說明 解決方式
在模組層級直接建立 FastAPI() 會導致全域單例,測試時無法重置狀態。 使用 factory,僅在需要時呼叫 create_app()
設定檔硬寫在程式碼內 難以在不同環境切換,且容易把機密資訊寫進版本控制。 把設定抽成類別或 pydantic.BaseSettings,並支援環境變數。
路由與中介軟體散落在多個檔案卻未統一註冊 可能遺漏某些路由,導致 404。 在工廠內統一 include_routeradd_middleware,確保所有模組都被載入。
忘記在測試結束後關閉資源 例如資料庫連線未關閉,會佔用端口。 使用 @app.on_event("shutdown")pytestfixture 來清理。
__init__.py 中執行副作用 會在 import 時執行不必要的程式,影響啟動速度。 保持 __init__.py 輕量,僅匯出 create_app

最佳實踐小結

  1. 工廠函式接受設定類別路徑,讓 CLI、Dockerfile、CI 都能簡單切換。
  2. 將路由、模型、服務層分離,每個子模組只負責單一職責。
  3. 使用 app.stateContextVar 保存全域資源,避免使用全域變數。
  4. 在測試中使用 TestClient,每個測試函式都呼叫 create_app(),確保隔離。
  5. 加入型別提示與 docstring,提升 IDE 補全與自動文件生成的品質。

實際應用場景

場景 為何需要 Application Factory
微服務叢集 每個服務都有不同的環境變數與資料庫,工廠讓部署腳本只改參數即可。
CI/CD 自動化測試 測試 pipeline 會多次啟動 FastAPI,使用工廠可以快速產生乾淨的測試環境。
多版本 API 透過 include_router 並在工廠內根據設定載入不同版本的路由,維護成本降低。
動態插件 需要在運行時根據外部設定載入額外的路由或中介軟體,工廠提供了「組裝」的彈性。
本機開發與容器化 開發時使用 DevConfig(開啟 reload、debug),上線時只換成 ProdConfig,程式碼不需要變動。

總結

Application Factory Pattern 是構建可維護、可測試、可擴充 FastAPI 專案的基礎建築。透過延遲建立 FastAPI 實例,我們可以:

  • 清晰分離設定、路由與中介軟體
  • 在不同環境間輕鬆切換(開發 / 測試 / 生產)。
  • 提升測試效率與可靠性,每次測試都有獨立的 app。

只要遵循本文的目錄結構、工廠實作方式與最佳實踐,你的 FastAPI 專案將具備良好的可讀性與可維護性,未來再加入新功能或部署到雲端都會更加順暢。快把這套模式套用到自己的專案中,體驗 乾淨、彈性、可測 的開發流程吧!