# 

# 06-1. 依賴注入基礎

&gt; ⏱️ **閱讀時間：** 15 分鐘
&gt; 🎯 **難度：** ⭐⭐ (中級)

---

## 🤔 一句話解釋

**依賴注入（DI）是一種設計模式，讓函數的依賴由外部提供，而不是自己創建，提高程式碼的可測試性和可維護性。**

---

## 🔄 沒有 DI vs 有 DI

```python
# ❌ 沒有依賴注入
def get_users():
    db = Database(&#34;postgresql://localhost/db&#34;)  # 硬編碼
    return db.query(&#34;SELECT * FROM users&#34;)

# ✅ 有依賴注入
def get_users(db: Database):  # 依賴由外部提供
    return db.query(&#34;SELECT * FROM users&#34;)
```

```
沒有 DI：
┌────────────┐     ┌──────────────┐
│  get_users │────▶│   Database   │ 內部建立
└────────────┘     └──────────────┘
緊密耦合，難以測試

有 DI：
┌────────────┐     ┌──────────────┐
│  外部      │────▶│   Database   │
└────────────┘     └──────────────┘
       │                  │
       └─────────────────▶│
              ┌────────────┐
              │  get_users │ 接收依賴
              └────────────┘
鬆散耦合，易於測試
```

---

## 🎯 依賴注入的好處

| 好處 | 說明 |
|------|------|
| **可測試性** | 可以注入 mock 物件進行測試 |
| **可維護性** | 依賴關係清晰，易於修改 |
| **可重用性** | 相同的依賴可以在多處使用 |
| **鬆散耦合** | 組件之間不直接相依 |

---

## 📦 FastAPI 依賴注入

### 基本用法

```python
from fastapi import FastAPI, Depends

app = FastAPI()

# 定義依賴函數
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    return {&#34;q&#34;: q, &#34;skip&#34;: skip, &#34;limit&#34;: limit}

# 使用依賴
@app.get(&#34;/items&#34;)
async def read_items(commons: dict = Depends(common_parameters)):
    return {&#34;commons&#34;: commons}

@app.get(&#34;/users&#34;)
async def read_users(commons: dict = Depends(common_parameters)):
    return {&#34;commons&#34;: commons}
```

### 依賴的執行順序

```
客戶端請求
    │
    ▼
┌─────────────────┐
│ 解析依賴樹      │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ 執行依賴函數    │ ← common_parameters()
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ 執行端點函數    │ ← read_items()
└─────────────────┘
    │
    ▼
返回回應
```

---

## 🔧 依賴類型

### 函數依賴

```python
from fastapi import FastAPI, Depends, Query

app = FastAPI()

def pagination(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100)
):
    &#34;&#34;&#34;分頁參數依賴&#34;&#34;&#34;
    return {
        &#34;skip&#34;: (page - 1) * page_size,
        &#34;limit&#34;: page_size
    }

@app.get(&#34;/items&#34;)
async def get_items(pagination: dict = Depends(pagination)):
    # pagination = {&#34;skip&#34;: 0, &#34;limit&#34;: 20}
    return {&#34;pagination&#34;: pagination}
```

### 類別依賴

```python
from fastapi import FastAPI, Depends, Query

app = FastAPI()

class Pagination:
    &#34;&#34;&#34;分頁類別依賴&#34;&#34;&#34;

    def __init__(
        self,
        page: int = Query(1, ge=1),
        page_size: int = Query(20, ge=1, le=100)
    ):
        self.page = page
        self.page_size = page_size
        self.skip = (page - 1) * page_size
        self.limit = page_size

@app.get(&#34;/items&#34;)
async def get_items(pagination: Pagination = Depends(Pagination)):
    return {
        &#34;page&#34;: pagination.page,
        &#34;skip&#34;: pagination.skip,
        &#34;limit&#34;: pagination.limit
    }

# 簡化寫法（Depends() 可以省略參數）
@app.get(&#34;/users&#34;)
async def get_users(pagination: Pagination = Depends()):
    return {&#34;page&#34;: pagination.page}
```

### 可呼叫類別

```python
from fastapi import FastAPI, Depends

app = FastAPI()

class DatabaseChecker:
    &#34;&#34;&#34;可呼叫的類別依賴&#34;&#34;&#34;

    def __init__(self, db_name: str):
        self.db_name = db_name

    def __call__(self):
        # 檢查資料庫連線
        return {&#34;database&#34;: self.db_name, &#34;status&#34;: &#34;connected&#34;}

# 建立依賴實例
check_primary_db = DatabaseChecker(&#34;primary&#34;)
check_replica_db = DatabaseChecker(&#34;replica&#34;)

@app.get(&#34;/primary&#34;)
async def primary_endpoint(db_status: dict = Depends(check_primary_db)):
    return db_status

@app.get(&#34;/replica&#34;)
async def replica_endpoint(db_status: dict = Depends(check_replica_db)):
    return db_status
```

---

## 📊 非同步依賴

```python
from fastapi import FastAPI, Depends
import httpx

app = FastAPI()

async def get_external_data():
    &#34;&#34;&#34;非同步依賴&#34;&#34;&#34;
    async with httpx.AsyncClient() as client:
        response = await client.get(&#34;https://api.example.com/data&#34;)
        return response.json()

@app.get(&#34;/items&#34;)
async def get_items(external_data: dict = Depends(get_external_data)):
    return {&#34;external&#34;: external_data}
```

---

## 🔄 巢狀依賴

### 依賴之間的依賴

```python
from fastapi import FastAPI, Depends, Query

app = FastAPI()

def query_extractor(q: str = Query(None)):
    &#34;&#34;&#34;提取查詢參數&#34;&#34;&#34;
    return q

def query_or_default(q: str = Depends(query_extractor)):
    &#34;&#34;&#34;依賴於 query_extractor&#34;&#34;&#34;
    if q:
        return q
    return &#34;default&#34;

@app.get(&#34;/items&#34;)
async def get_items(query: str = Depends(query_or_default)):
    return {&#34;query&#34;: query}
```

### 多層依賴

```python
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()

async def verify_token(x_token: str = Header()):
    &#34;&#34;&#34;驗證 Token&#34;&#34;&#34;
    if x_token != &#34;secret-token&#34;:
        raise HTTPException(status_code=401, detail=&#34;Invalid token&#34;)
    return x_token

async def get_current_user(token: str = Depends(verify_token)):
    &#34;&#34;&#34;取得當前使用者（依賴於 verify_token）&#34;&#34;&#34;
    # 根據 token 查詢使用者
    return {&#34;user_id&#34;: 1, &#34;username&#34;: &#34;john&#34;}

async def get_user_permissions(
    user: dict = Depends(get_current_user)
):
    &#34;&#34;&#34;取得使用者權限（依賴於 get_current_user）&#34;&#34;&#34;
    return {&#34;user&#34;: user, &#34;permissions&#34;: [&#34;read&#34;, &#34;write&#34;]}

@app.get(&#34;/admin&#34;)
async def admin_endpoint(
    permissions: dict = Depends(get_user_permissions)
):
    return permissions
```

依賴樹：

```
admin_endpoint
    │
    └── get_user_permissions
            │
            └── get_current_user
                    │
                    └── verify_token
```

---

## ⚙️ 路徑操作裝飾器中的依賴

```python
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()

async def verify_key(x_key: str = Header()):
    if x_key != &#34;valid-key&#34;:
        raise HTTPException(status_code=403, detail=&#34;Invalid key&#34;)
    return x_key

# 依賴在裝飾器中，不注入到函數參數
@app.get(&#34;/items&#34;, dependencies=[Depends(verify_key)])
async def get_items():
    return {&#34;items&#34;: []}

@app.get(&#34;/users&#34;, dependencies=[Depends(verify_key)])
async def get_users():
    return {&#34;users&#34;: []}
```

---

## 🌐 全域依賴

```python
from fastapi import FastAPI, Depends, Header, HTTPException

async def verify_api_key(x_api_key: str = Header()):
    if x_api_key != &#34;my-api-key&#34;:
        raise HTTPException(status_code=403)

# 所有端點都會執行這個依賴
app = FastAPI(dependencies=[Depends(verify_api_key)])

@app.get(&#34;/items&#34;)
async def get_items():
    return {&#34;items&#34;: []}

@app.get(&#34;/users&#34;)
async def get_users():
    return {&#34;users&#34;: []}
```

---

## 📝 實戰範例：認證依賴

```python
from fastapi import FastAPI, Depends, HTTPException, Header
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class User(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool = True
    is_admin: bool = False

# 模擬資料庫
fake_users = {
    &#34;token_user1&#34;: User(id=1, username=&#34;john&#34;, email=&#34;john@example.com&#34;),
    &#34;token_admin&#34;: User(
        id=2, username=&#34;admin&#34;, email=&#34;admin@example.com&#34;, is_admin=True
    ),
}

async def get_token(authorization: str = Header()):
    &#34;&#34;&#34;提取 Token&#34;&#34;&#34;
    if not authorization.startswith(&#34;Bearer &#34;):
        raise HTTPException(status_code=401, detail=&#34;Invalid authorization header&#34;)
    return authorization.replace(&#34;Bearer &#34;, &#34;&#34;)

async def get_current_user(token: str = Depends(get_token)) -&gt; User:
    &#34;&#34;&#34;取得當前使用者&#34;&#34;&#34;
    user = fake_users.get(token)
    if not user:
        raise HTTPException(status_code=401, detail=&#34;Invalid token&#34;)
    return user

async def get_current_active_user(
    user: User = Depends(get_current_user)
) -&gt; User:
    &#34;&#34;&#34;確保使用者是啟用狀態&#34;&#34;&#34;
    if not user.is_active:
        raise HTTPException(status_code=403, detail=&#34;Inactive user&#34;)
    return user

async def get_admin_user(
    user: User = Depends(get_current_active_user)
) -&gt; User:
    &#34;&#34;&#34;確保使用者是管理員&#34;&#34;&#34;
    if not user.is_admin:
        raise HTTPException(status_code=403, detail=&#34;Admin required&#34;)
    return user

# 一般使用者端點
@app.get(&#34;/users/me&#34;)
async def read_me(user: User = Depends(get_current_active_user)):
    return user

# 管理員端點
@app.get(&#34;/admin/users&#34;)
async def read_all_users(admin: User = Depends(get_admin_user)):
    return list(fake_users.values())
```

---

## ✅ 重點總結

### 依賴類型

| 類型 | 說明 |
|------|------|
| **函數** | 簡單依賴 |
| **類別** | 需要 `__init__` 參數 |
| **可呼叫** | 實作 `__call__` 方法 |

### 使用方式

| 方式 | 用途 |
|------|------|
| `Depends(func)` | 函數參數中使用 |
| `dependencies=[...]` | 裝飾器中使用 |
| `app = FastAPI(dependencies=[...])` | 全域使用 |

### 最佳實踐

1. 保持依賴函數單一職責
2. 使用類型提示提高可讀性
3. 善用巢狀依賴組織邏輯
4. 考慮依賴的效能影響

---

## 🎤 面試這樣答

### Q: 什麼是依賴注入？FastAPI 如何實現？

**答案：**

&gt; **依賴注入**是一種設計模式，讓函數的依賴由外部提供，而非自己創建。
&gt;
&gt; **FastAPI 實現方式：**
&gt;
&gt; ```python
&gt; from fastapi import Depends
&gt;
&gt; def get_db():
&gt;     db = Database()
&gt;     try:
&gt;         yield db
&gt;     finally:
&gt;         db.close()
&gt;
&gt; @app.get(&#34;/users&#34;)
&gt; async def get_users(db: Database = Depends(get_db)):
&gt;     return db.get_all_users()
&gt; ```
&gt;
&gt; **好處：**
&gt; 1. 可測試性：可以注入 mock
&gt; 2. 可維護性：依賴關係清晰
&gt; 3. 程式碼重用：相同依賴多處使用

---

**下一篇：** [06-2. yield 依賴](./06-2)

---

最後更新：2025-12-18


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/fastapi/06-1/  

