本文 AI 產出,尚未審核
FastAPI – 模組化應用結構(Application Setup)
簡介
在開發 API 時,隨著功能逐漸增多,單一檔案的程式碼會變得難以維護、測試與部署。模組化的應用結構 能讓專案保持清晰的層次、方便團隊協作,且在部署到容器或伺服器時更易管理。FastAPI 本身支援 APIRouter、依賴注入(Dependency Injection)與 Pydantic 設定模型,這些特性正好配合模組化的設計理念。本文將一步步說明如何在 FastAPI 中建立一套易於擴充、易於測試的模組化專案。
核心概念
1. 為什麼需要模組化
- 可讀性:每個功能區塊都有自己的目錄與檔案,開發者只需關注相關模組。
- 可重用性:路由、服務、資料庫操作等可以在不同專案間搬移。
- 測試便利:單元測試可以針對獨立模組撰寫,避免整體啟動成本。
- 部署彈性:設定、環境變數與啟動腳本分離,讓 CI/CD 更簡潔。
2. 基本目錄結構
my_fastapi_app/
├─ app/
│ ├─ __init__.py
│ ├─ main.py # 入口點
│ ├─ api/
│ │ ├─ __init__.py
│ │ ├─ router.py # 統一匯入子路由
│ │ ├─ users.py # 使用者相關路由
│ │ └─ items.py # 商品相關路由
│ ├─ core/
│ │ ├─ __init__.py
│ │ ├─ config.py # 設定模型
│ │ └─ security.py # 認證相關工具
│ ├─ db/
│ │ ├─ __init__.py
│ │ ├─ models.py # ORM 模型
│ │ └─ session.py # DB session
│ └─ services/
│ ├─ __init__.py
│ └─ user_service.py # 業務邏輯
├─ tests/
│ ├─ __init__.py
│ └─ test_users.py
├─ .env # 環境變數
├─ requirements.txt
└─ alembic/ # DB migration (optional)
重點:
app目錄內依功能分層,api放路由、core放共用設定與工具、db放資料庫相關、services放業務邏輯。
3. APIRouter 的使用
# app/api/users.py
from fastapi import APIRouter, Depends, HTTPException, status
from ..services.user_service import UserService
from ..models import UserCreate, UserRead
router = APIRouter(prefix="/users", tags=["users"])
@router.post("/", response_model=UserRead, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate, service: UserService = Depends()):
"""
建立新使用者
"""
return await service.create_user(payload)
# app/api/router.py
from fastapi import APIRouter
from . import users, items
api_router = APIRouter()
api_router.include_router(users.router)
api_router.include_router(items.router)
# app/main.py
from fastapi import FastAPI
from .api.router import api_router
from .core.config import settings
app = FastAPI(
title=settings.PROJECT_NAME,
version="0.1.0",
description="示範 FastAPI 模組化結構"
)
app.include_router(api_router)
說明:每個子模組只負責自己的路由,
router.py統一把它們匯入main.py,保持入口檔案乾淨。
4. 設定管理(Config)
# app/core/config.py
from pydantic import BaseSettings, Field
class Settings(BaseSettings):
PROJECT_NAME: str = Field(..., env="PROJECT_NAME")
DEBUG: bool = Field(False, env="DEBUG")
DATABASE_URL: str = Field(..., env="DATABASE_URL")
SECRET_KEY: str = Field(..., env="SECRET_KEY")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
使用 settings 於程式任何需要的地方,不再硬編碼,且 .env 檔案可在不同環境切換。
5. 依賴注入(Dependency Injection)管理 DB Session
# app/db/session.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
from ..core.config import settings
engine = create_engine(settings.DATABASE_URL, echo=settings.DEBUG)
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
在服務層或路由中直接 Depends(get_db),保持資料庫連線的生命週期一致。
6. 業務服務層(Service Layer)
# app/services/user_service.py
from typing import List
from fastapi import Depends
from ..db.session import get_db
from sqlalchemy.orm import Session
from ..models import User, UserCreate, UserRead
class UserService:
def __init__(self, db: Session = Depends(get_db)):
self.db = db
async def create_user(self, payload: UserCreate) -> UserRead:
user = User(**payload.dict())
self.db.add(user)
self.db.commit()
self.db.refresh(user)
return UserRead.from_orm(user)
async def list_users(self) -> List[UserRead]:
users = self.db.query(User).all()
return [UserRead.from_orm(u) for u in users]
好處:路由只負責請求/回應,所有資料庫操作與商業邏輯集中在
services,便於單元測試與未來重構。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 |
|---|---|---|
把所有路由寫在 main.py |
隨著功能增長,檔案變得龐大且難以閱讀 | 使用 APIRouter 分模組,並在 router.py 統一匯入 |
| 硬編碼設定值 | 部署到不同環境需要手動修改程式 | Pydantic BaseSettings + .env,讓設定與程式碼分離 |
| 在路由內直接建立 DB 連線 | 每個請求都會重複建立連線,資源浪費 | 使用 依賴注入 (Depends(get_db)) 讓連線在請求結束時自動關閉 |
| 在服務層直接呼叫外部 API | 測試時會觸發真實請求,難以斷言 | 把外部 API 包裝成 client class,在測試時使用 Mock |
| 忽略例外處理 | 未捕捉的例外會讓服務直接 500 錯誤 | 使用 FastAPI 的 Exception Handler,統一回傳錯誤格式 |
最佳實踐小結
- 目錄分層:路由、設定、資料庫、服務分開管理。
- 依賴注入:所有外部資源(DB、Redis、外部 API)皆以
Depends方式取得。 - 型別提示與 Pydantic:盡量使用
BaseModel定義請求與回應,讓 IDE 與 FastAPI 自動產生文件。 - 測試友好:每個模組都能獨立
import,在tests/中針對服務層與路由層寫測試。
實際應用場景
| 場景 | 需求 | 模組化帶來的好處 |
|---|---|---|
| 微服務 | 每個服務只提供特定領域的 API,如使用者、商品、訂單 | 只需在 docker-compose.yml 中掛載對應的子目錄,快速擴充或縮減功能 |
| 多租戶 SaaS | 同一套程式碼支援多家客戶,設定不同資料庫 | 透過 settings 動態載入不同的 DATABASE_URL,服務層只需要 Depends(get_db) 即可 |
| CI/CD | 每次 PR 必須跑單元測試、整合測試 | 因為路由、服務與 DB 都是獨立模組,測試腳本只要 import app.main 即可啟動測試環境 |
| 版本升級 | 需要在不影響現有 API 的情況下加入新功能 | 新功能寫在新的 router、service 中,舊的路由不變,且可以在 router.py 中設定 include_in_schema=False 暫時隱藏 |
總結
模組化的 FastAPI 應用結構不僅讓程式碼更乾淨、易於維護,也為團隊協作、測試自動化與多環境部署奠定基礎。透過 APIRouter、依賴注入、Pydantic 設定模型 與 服務層 的分離,我們可以在專案成長的同時,保持高可讀性與高擴充性。從今天開始,依照本文的目錄規劃與最佳實踐,為你的 FastAPI 專案打造堅實的模組化基礎吧!