抽象概念:API 的「標準化合約」
OpenAPI (Swagger) = API 的身分證 + 使用手冊 + 自動化工具
- 定義輸入格式 → 你需要提供什麼資料
- 定義輸出格式 → 你會得到什麼回應
- 定義錯誤處理 → 出錯時會發生什麼
- 自動生成文件 → 團隊協作的可視化界面
- 程式碼生成 → 減少重複工作
OpenAPI(前身為 Swagger)是現代 API 開發中最重要的標準之一。它不僅僅是一份文件,更是整個 API 生命週期的核心工具。想像一下,如果共用契約是建築的「材料規格書」,那麼 OpenAPI 就是完整的「建築藍圖」,詳細描述每個房間的佈局、門窗位置、電路配置等所有細節。
在沒有 OpenAPI 之前,API 開發常常面臨以下困境:
情境重現:API 開發的混亂時代
後端工程師:「我做好 API 了!」
前端工程師:「要怎麼呼叫?參數是什麼?」
後端工程師:「你看一下程式碼就知道了...」
前端工程師:「我不會看後端程式碼啊!」
測試工程師:「我要怎麼測試?有文件嗎?」
產品經理:「這個 API 到底有什麼功能?」
這種場景是否似曾相識?OpenAPI 的出現就是為了解決這些溝通與協作問題。
1. 文件即程式碼 (Documentation as Code)
傳統的 API 文件容易過時,因為程式碼更新後,開發者往往忘記同步更新文件。OpenAPI 讓文件與程式碼緊密結合,當 API 變更時,文件也會自動同步更新。
# 這不只是文件,還是可執行的規格
paths:
/users/{userId}:
get:
summary: 取得使用者資訊
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: 成功取得使用者資訊
content:
application/json:
schema:
$ref: "#/components/schemas/User"
2. 契約優先開發 (Contract-First Development)
OpenAPI 支援「契約優先」的開發模式,讓前後端團隊可以先定義 API 契約,然後各自根據契約進行開發。
傳統開發流程:
後端完成 → 前端開始 → 發現問題 → 來回修改
契約優先流程:
定義契約 → 前後端並行開發 → 集成測試 → 快速交付
3. 自動化工具生態系統
OpenAPI 擁有豐富的工具生態系統,可以自動化許多重複性工作:
一個完整的 OpenAPI 文件包含以下核心元素:
基本資訊區塊 (Info Object)
openapi: 3.0.3
info:
title: 電商平台 API
description: |
提供完整的電商功能,包括商品管理、訂單處理、使用者管理等核心功能。
## 認證方式
使用 Bearer Token 進行認證,請在 Authorization Header 中包含您的 API Token。
## 限制速率
每個 API Token 每分鐘最多可發送 1000 次請求。
version: 2.1.0
contact:
name: API 支援團隊
email: api-support@yourcompany.com
url: https://docs.yourcompany.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
伺服器設定 (Servers)
servers:
- url: https://api.yourcompany.com/v2
description: 正式環境
- url: https://staging-api.yourcompany.com/v2
description: 測試環境
- url: http://localhost:3000/v2
description: 本地開發環境
路徑與操作 (Paths & Operations)
paths:
/products:
get:
tags:
- 商品管理
summary: 取得商品列表
description: |
取得商品列表,支援分頁、篩選和排序功能。
### 使用範例
- 取得第一頁商品:`GET /products?page=1&limit=20`
- 搜尋特定商品:`GET /products?search=iPhone`
- 價格範圍篩選:`GET /products?minPrice=1000&maxPrice=5000`
parameters:
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/LimitParam"
- name: search
in: query
description: 商品名稱或描述的關鍵字搜尋
schema:
type: string
example: "iPhone 14"
- name: category
in: query
description: 商品分類篩選
schema:
type: string
enum: [electronics, clothing, books, home]
- name: minPrice
in: query
description: 最低價格
schema:
type: number
minimum: 0
- name: maxPrice
in: query
description: 最高價格
schema:
type: number
minimum: 0
responses:
"200":
description: 成功取得商品列表
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
data:
type: object
properties:
products:
type: array
items:
$ref: "#/components/schemas/Product"
pagination:
$ref: "#/components/schemas/Pagination"
error:
type: object
nullable: true
example: null
examples:
成功範例:
summary: 正常回應範例
value:
success: true
data:
products:
- id: "prod-001"
name: "iPhone 14 Pro"
price: 32900
category: "electronics"
pagination:
page: 1
limit: 20
total: 156
totalPages: 8
error: null
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalServerError"
資料模型定義 (Components/Schemas)
components:
schemas:
Product:
type: object
required:
- id
- name
- price
- category
properties:
id:
type: string
description: 商品唯一識別碼
example: "prod-001"
name:
type: string
description: 商品名稱
minLength: 1
maxLength: 200
example: "iPhone 14 Pro"
description:
type: string
description: 商品描述
maxLength: 2000
example: "Apple 最新旗艦手機,配備 A16 Bionic 晶片"
price:
type: number
description: 商品價格(新台幣)
minimum: 0
example: 32900
originalPrice:
type: number
description: 原始價格(用於顯示折扣)
minimum: 0
example: 36900
category:
type: string
description: 商品分類
enum: [electronics, clothing, books, home]
example: "electronics"
images:
type: array
description: 商品圖片 URL 列表
items:
type: string
format: uri
example:
- "https://cdn.example.com/images/iphone14-1.jpg"
- "https://cdn.example.com/images/iphone14-2.jpg"
stock:
type: integer
description: 庫存數量
minimum: 0
example: 50
isActive:
type: boolean
description: 商品是否啟用
example: true
createdAt:
type: string
format: date-time
description: 建立時間
example: "2024-01-15T10:30:00Z"
updatedAt:
type: string
format: date-time
description: 最後更新時間
example: "2024-01-20T14:45:00Z"
Pagination:
type: object
required:
- page
- limit
- total
- totalPages
properties:
page:
type: integer
description: 目前頁數
minimum: 1
example: 1
limit:
type: integer
description: 每頁筆數
minimum: 1
maximum: 100
example: 20
total:
type: integer
description: 總筆數
minimum: 0
example: 156
totalPages:
type: integer
description: 總頁數
minimum: 0
example: 8
1. 安全性定義 (Security Schemes)
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
使用 JWT Token 進行認證。請在 Authorization Header 中提供 Bearer Token。
範例:`Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: |
使用 API Key 進行認證。請在 X-API-Key Header 中提供您的 API Key。
# 應用到特定端點
paths:
/products:
get:
security: [] # 公開端點,不需要認證
post:
security:
- BearerAuth: [] # 需要 Bearer Token
/admin/users:
get:
security:
- BearerAuth: []
- ApiKeyAuth: [] # 同時支援兩種認證方式
2. 可重複使用的元件 (Reusable Components)
components:
parameters:
PageParam:
name: page
in: query
description: 頁數
required: false
schema:
type: integer
minimum: 1
default: 1
LimitParam:
name: limit
in: query
description: 每頁筆數
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
BadRequest:
description: 請求參數錯誤
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
參數驗證失敗:
summary: 參數格式不正確
value:
success: false
data: null
error:
code: 400001
message: "參數驗證失敗"
details:
- field: "price"
message: "price 必須是正數"
InternalServerError:
description: 伺服器內部錯誤
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
伺服器錯誤:
summary: 系統內部錯誤
value:
success: false
data: null
error:
code: 500001
message: "系統暫時無法處理請求,請稍後再試"
3. 回調和 Webhooks (Callbacks & Webhooks)
paths:
/orders:
post:
summary: 建立訂單
callbacks:
orderStatusChanged:
"{$request.body#/webhookUrl}":
post:
summary: 訂單狀態變更通知
requestBody:
content:
application/json:
schema:
type: object
properties:
orderId:
type: string
status:
type: string
enum: [pending, paid, shipped, delivered, cancelled]
timestamp:
type: string
format: date-time
1. 版本管理策略
# 語義化版本控制
info:
version: 2.1.0 # 主版本.次版本.修訂版本
# URL 版本控制
servers:
- url: https://api.example.com/v2
# Header 版本控制(進階)
components:
parameters:
ApiVersion:
name: Api-Version
in: header
schema:
type: string
enum: ["2.0", "2.1"]
default: "2.1"
2. 變更管理流程
# 使用標籤標記生命週期狀態
paths:
/legacy-endpoint:
get:
deprecated: true
summary: 舊版端點(即將棄用)
description: |
⚠️ **此端點已棄用**
請使用新的端點:`GET /v2/new-endpoint`
**棄用時間**:2024-06-01
**移除時間**:2024-12-01
/experimental-feature:
post:
tags: [實驗性功能]
summary: 實驗性功能(測試中)
description: |
🧪 **實驗性功能**
此功能正在測試階段,API 可能會有變更。
不建議在正式環境中使用。
情境:電商平台新增「商品評價系統」
讓我們透過一個實際案例來看 OpenAPI 如何促進團隊協作:
第一步:產品需求確認
產品經理提出需求:
「我們需要一個商品評價系統,讓用戶可以對購買的商品進行評分和留言」
第二步:API 契約設計(團隊協作)
# 第一版契約草稿(由後端主導,前端參與)
paths:
/products/{productId}/reviews:
post:
summary: 提交商品評價
description: |
用戶對已購買的商品提交評價。
**業務規則**:
- 只有購買過此商品的用戶才能評價
- 每個用戶對同一商品只能評價一次
- 評分範圍:1-5 分
parameters:
- name: productId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- rating
- comment
properties:
rating:
type: integer
minimum: 1
maximum: 5
description: 評分(1-5分)
comment:
type: string
minLength: 10
maxLength: 500
description: 評價內容
images:
type: array
maxItems: 5
items:
type: string
format: uri
description: 評價圖片(最多5張)
responses:
"201":
description: 評價提交成功
content:
application/json:
schema:
$ref: "#/components/schemas/ReviewResponse"
"400":
description: 參數錯誤
"403":
description: 未購買此商品或已評價過
"401":
description: 未登入
第三步:前端團隊回饋與調整
前端工程師:「我需要一個 API 來檢查用戶是否可以評價這個商品」
產品經理:「我們還需要取得商品的所有評價列表」
測試工程師:「需要明確定義圖片上傳的格式和大小限制」
第四步:契約迭代優化
# 第二版契約(整合團隊回饋)
paths:
# 檢查評價權限
/products/{productId}/reviews/check-permission:
get:
summary: 檢查用戶評價權限
description: 檢查當前用戶是否可以對此商品進行評價
responses:
"200":
content:
application/json:
schema:
type: object
properties:
canReview:
type: boolean
reason:
type: string
enum: [not_purchased, already_reviewed, pending_delivery]
# 取得評價列表
/products/{productId}/reviews:
get:
summary: 取得商品評價列表
parameters:
- $ref: "#/components/parameters/PageParam"
- $ref: "#/components/parameters/LimitParam"
- name: rating
in: query
description: 按評分篩選
schema:
type: integer
minimum: 1
maximum: 5
- name: sort
in: query
description: 排序方式
schema:
type: string
enum: [latest, oldest, highest_rating, lowest_rating]
default: latest
responses:
"200":
description: 成功取得評價列表
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
reviews:
type: array
items:
$ref: "#/components/schemas/Review"
pagination:
$ref: "#/components/schemas/Pagination"
statistics:
$ref: "#/components/schemas/ReviewStatistics"
components:
schemas:
Review:
type: object
properties:
id:
type: string
userId:
type: string
userName:
type: string
userAvatar:
type: string
format: uri
rating:
type: integer
minimum: 1
maximum: 5
comment:
type: string
images:
type: array
items:
type: string
format: uri
createdAt:
type: string
format: date-time
isVerifiedPurchase:
type: boolean
description: 是否為驗證購買
helpfulCount:
type: integer
description: 有用評價數
ReviewStatistics:
type: object
properties:
averageRating:
type: number
format: float
example: 4.2
totalReviews:
type: integer
example: 156
ratingDistribution:
type: object
properties:
"5":
type: integer
example: 89
"4":
type: integer
example: 42
"3":
type: integer
example: 15
"2":
type: integer
example: 7
"1":
type: integer
example: 3
1. 契約設計原則
# ✅ 好的做法:詳細且明確的描述
/users/{userId}/orders:
get:
summary: 取得用戶訂單列表
description: |
取得指定用戶的訂單列表,支援多種篩選條件。
**權限要求**:
- 用戶只能查看自己的訂單
- 管理員可以查看所有用戶的訂單
**分頁說明**:
- 預設每頁 20 筆
- 最大每頁 100 筆
- 按訂單建立時間倒序排列
parameters:
- name: status
in: query
description: |
訂單狀態篩選,可以指定多個狀態。
範例:
- 單一狀態:`?status=paid`
- 多重狀態:`?status=paid,shipped`
schema:
type: array
items:
type: string
enum: [pending, paid, shipped, delivered, cancelled, refunded]
style: form
explode: false
# ❌ 不好的做法:描述不清楚
/users/{userId}/orders:
get:
summary: 取得訂單
parameters:
- name: status
in: query
schema:
type: string
2. 錯誤處理標準化
components:
schemas:
# 統一的錯誤回應格式
StandardError:
type: object
required:
- success
- error
properties:
success:
type: boolean
enum: [false]
data:
type: object
nullable: true
example: null
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: 機器可讀的錯誤代碼
example: "PRODUCT_NOT_FOUND"
message:
type: string
description: 人類可讀的錯誤訊息
example: "找不到指定的商品"
details:
type: array
description: 詳細錯誤資訊(通常用於驗證錯誤)
items:
type: object
properties:
field:
type: string
example: "email"
message:
type: string
example: "電子郵件格式不正確"
trace_id:
type: string
description: 錯誤追蹤 ID,用於問題除錯
example: "req_1234567890abcdef"
responses:
# 可重用的錯誤回應
ValidationError:
description: 輸入驗證失敗
content:
application/json:
schema:
$ref: "#/components/schemas/StandardError"
examples:
email_validation:
summary: 電子郵件驗證失敗
value:
success: false
data: null
error:
code: "VALIDATION_FAILED"
message: "輸入資料驗證失敗"
details:
- field: "email"
message: "電子郵件格式不正確"
- field: "password"
message: "密碼長度至少需要 8 個字元"
trace_id: "req_1234567890abcdef"
3. 版本控制與向後相容性
# 範例:如何優雅地進行 API 演進
# v1 版本(現有)
/v1/products/{id}:
get:
responses:
"200":
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
price:
type: number
# v2 版本(新增功能,保持向後相容)
/v2/products/{id}:
get:
responses:
"200":
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
price:
type: number
# 新增欄位
currency:
type: string
default: "TWD"
variants:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
price:
type: number
# 標記為棄用但仍保留
old_price_field:
type: number
deprecated: true
description: "請使用 price 欄位"
案例背景:一個 10 人的開發團隊(3 前端、3 後端、2 測試、1 產品、1 DevOps)需要在 6 週內開發一個新的「會員積分系統」。
第 1 週:契約設計階段
# 團隊協作流程
1. 產品經理 + 後端主管:定義業務需求與基本 API 結構
2. 前端團隊加入:提供前端需求與資料格式建議
3. 測試團隊參與:確認測試案例覆蓋度
4. 全體評審:API 設計評審會議
協作成果:積分系統 API 契約
# 積分系統核心 API
paths:
/members/{memberId}/points:
get:
summary: 查詢會員積分
description: |
查詢會員的積分餘額和交易紀錄
**前端需求**:
- 需要顯示總積分
- 需要顯示即將到期的積分
- 需要分頁的交易紀錄
**測試重點**:
- 會員隱私權限控制
- 積分計算正確性
- 分頁功能
responses:
"200":
description: 成功取得積分資訊
content:
application/json:
schema:
type: object
properties:
currentPoints:
type: integer
description: 目前可用積分
example: 1250
expiringPoints:
type: array
description: 即將到期的積分
items:
type: object
properties:
points:
type: integer
expiryDate:
type: string
format: date
transactions:
type: array
description: 積分交易紀錄
items:
$ref: "#/components/schemas/PointTransaction"
pagination:
$ref: "#/components/schemas/Pagination"
/members/{memberId}/points/transactions:
post:
summary: 建立積分交易
description: |
新增積分交易(消費獲得積分、兌換消費、管理員調整等)
**後端實作重點**:
- 交易原子性保證
- 積分餘額即時更新
- 防止重複交易
**測試案例**:
- 並發交易處理
- 負積分防護
- 交易記錄完整性
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- type
- points
- description
properties:
type:
type: string
enum: [earn, redeem, expire, admin_adjust]
points:
type: integer
description: 積分數量(正數表示增加,負數表示扣除)
description:
type: string
description: 交易說明
orderId:
type: string
description: 關聯的訂單 ID(當 type 為 earn 或 redeem 時必填)
expiryDate:
type: string
format: date
description: 積分到期日(當 type 為 earn 時必填)
adminNote:
type: string
description: 管理員備註(當 type 為 admin_adjust 時必填)
第 2-3 週:並行開發階段
前端團隊:
- 根據契約建立 TypeScript 型別定義
- 建立 Mock Server 進行頁面開發
- 實作積分查詢和交易紀錄頁面
後端團隊:
- 根據契約實作 API 端點
- 設計資料庫 Schema
- 實作業務邏輯和交易安全機制
測試團隊:
- 根據契約編寫自動化測試
- 準備測試資料和測試案例
- 設定 API 契約測試
第 4 週:整合測試階段
# 使用 OpenAPI 自動生成的測試工具
test_scenarios:
- name: "正常積分查詢流程"
steps:
- operation: GET /members/test-user-001/points
expected_status: 200
response_validation:
- schema_compliance: true
- data_type_check: true
- business_rule_check:
- currentPoints >= 0
- pagination.total >= 0
- name: "積分交易安全性測試"
steps:
- operation: POST /members/other-user/points/transactions
auth: user-001-token
expected_status: 403
description: "確保用戶無法操作他人積分"
- name: "並發積分交易測試"
concurrent_requests: 10
operation: POST /members/test-user-001/points/transactions
data:
type: redeem
points: -100
description: "兌換測試"
validation:
- final_balance_consistency: true
- transaction_count_accuracy: true
第 5-6 週:文件與交付階段
團隊利用 OpenAPI 自動生成: