533 lines
12 KiB
Markdown
533 lines
12 KiB
Markdown
|
|
---
|
|||
|
|
name: be-api-design
|
|||
|
|
description: "Backend Agent 使用此技能設計 API 規格。根據 PRD 產出 OpenAPI 3.0 規格,包含端點、請求/回應結構、錯誤處理。觸發時機:PRD 通過後(Stage 4)。"
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# /be-api-design — API 設計
|
|||
|
|
|
|||
|
|
Backend Agent 使用此技能設計 API 規格。
|
|||
|
|
|
|||
|
|
## 職責
|
|||
|
|
|
|||
|
|
1. 分析 PRD 中的功能性需求
|
|||
|
|
2. 使用 `design-an-interface` 探索多種 API 設計方案
|
|||
|
|
3. 設計 RESTful API 端點
|
|||
|
|
4. 定義請求/回應 schema
|
|||
|
|
5. 設計錯誤處理機制
|
|||
|
|
6. 產出 OpenAPI 3.0 規格
|
|||
|
|
|
|||
|
|
## 輸入
|
|||
|
|
|
|||
|
|
- PRD 文件 (`docs/prd/{date}-{feature}.md`)
|
|||
|
|
- 現有 API 風格 (專案中現有的 API 規格)
|
|||
|
|
- 資料模型上下文
|
|||
|
|
|
|||
|
|
## 輸出
|
|||
|
|
|
|||
|
|
- API 規格文件: `docs/api/{date}-{feature}.yaml` (OpenAPI 3.0)
|
|||
|
|
|
|||
|
|
## 流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
讀取 PRD
|
|||
|
|
↓
|
|||
|
|
識別資源與操作
|
|||
|
|
↓
|
|||
|
|
呼叫 design-an-interface 探索 2-3 種設計方案
|
|||
|
|
↓
|
|||
|
|
選定最佳方案
|
|||
|
|
↓
|
|||
|
|
定義 OpenAPI Schema
|
|||
|
|
↓
|
|||
|
|
設計錯誤處理
|
|||
|
|
↓
|
|||
|
|
安全性審查
|
|||
|
|
↓
|
|||
|
|
產出 OpenAPI 文件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步驟說明
|
|||
|
|
|
|||
|
|
**1. 讀取 PRD**
|
|||
|
|
|
|||
|
|
從 PRD 中提取所有功能性需求,識別:
|
|||
|
|
- 資源(名詞):使用者、訂單、商品等
|
|||
|
|
- 操作(動詞):建立、讀取、更新、刪除等
|
|||
|
|
- 關係:資源之間的關聯(一對多、多對多)
|
|||
|
|
- 非功能性需求:分頁、速率限制、認證等
|
|||
|
|
|
|||
|
|
**2. 識別資源與操作**
|
|||
|
|
|
|||
|
|
將功能性需求映射為 RESTful 資源:
|
|||
|
|
- 每個名詞 → 潛在資源
|
|||
|
|
- 每個動詞 → HTTP method
|
|||
|
|
- 每個關係 → 巢狀資源或獨立端點
|
|||
|
|
|
|||
|
|
**3. 呼叫 design-an-interface**
|
|||
|
|
|
|||
|
|
使用 `design-an-interface` 技能,產生 2-3 種截然不同的 API 設計方案:
|
|||
|
|
- 方案 A:最小化方法數(每個資源 1-3 個端點)
|
|||
|
|
- 方案 B:最大化彈性(支援多種使用情境)
|
|||
|
|
- 方案 C:最佳化最常見的操作
|
|||
|
|
|
|||
|
|
比較各方案的優劣,選定最佳設計。
|
|||
|
|
|
|||
|
|
**4. 定義 OpenAPI Schema**
|
|||
|
|
|
|||
|
|
使用下方模板產出完整的 OpenAPI 3.0 規格。
|
|||
|
|
|
|||
|
|
**5. 安全性審查**
|
|||
|
|
|
|||
|
|
確認所有端點都有適當的認證機制和權限控制。
|
|||
|
|
|
|||
|
|
**6. 產出文件**
|
|||
|
|
|
|||
|
|
儲存至 `docs/api/{date}-{feature}.yaml`。
|
|||
|
|
|
|||
|
|
## 設計原則
|
|||
|
|
|
|||
|
|
### RESTful 設計
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
資源導向:
|
|||
|
|
- URL 代表資源,而非動作
|
|||
|
|
- HTTP 方法代表動作
|
|||
|
|
|
|||
|
|
範例:
|
|||
|
|
GET /api/v1/users # 列出使用者
|
|||
|
|
GET /api/v1/users/{id} # 取得特定使用者
|
|||
|
|
POST /api/v1/users # 建立使用者
|
|||
|
|
PUT /api/v1/users/{id} # 完整更新
|
|||
|
|
PATCH /api/v1/users/{id} # 部分更新
|
|||
|
|
DELETE /api/v1/users/{id} # 刪除使用者
|
|||
|
|
|
|||
|
|
巢狀資源:
|
|||
|
|
GET /api/v1/users/{id}/orders # 取得使用者的訂單
|
|||
|
|
POST /api/v1/users/{id}/orders # 為使用者建立訂單
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### HTTP 狀態碼
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
成功:
|
|||
|
|
200: OK # GET, PUT, PATCH, DELETE 成功
|
|||
|
|
201: Created # POST 成功建立資源
|
|||
|
|
204: No Content # DELETE 成功,無回應內容
|
|||
|
|
|
|||
|
|
客戶端錯誤:
|
|||
|
|
400: Bad Request # 請求格式錯誤
|
|||
|
|
401: Unauthorized # 未認證
|
|||
|
|
403: Forbidden # 無權限
|
|||
|
|
404: Not Found # 資源不存在
|
|||
|
|
409: Conflict # 資源衝突 (如重複)
|
|||
|
|
422: Unprocessable Entity # 驗證錯誤
|
|||
|
|
429: Too Many Requests # 速率限制
|
|||
|
|
|
|||
|
|
伺服器錯誤:
|
|||
|
|
500: Internal Server Error # 伺服器內部錯誤
|
|||
|
|
502: Bad Gateway # 上游服務錯誤
|
|||
|
|
503: Service Unavailable # 服務暫時不可用
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 回應格式
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
成功回應:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
data:
|
|||
|
|
type: object
|
|||
|
|
meta:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
page:
|
|||
|
|
type: integer
|
|||
|
|
limit:
|
|||
|
|
type: integer
|
|||
|
|
total:
|
|||
|
|
type: integer
|
|||
|
|
total_pages:
|
|||
|
|
type: integer
|
|||
|
|
|
|||
|
|
錯誤回應:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
error:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
code:
|
|||
|
|
type: string
|
|||
|
|
message:
|
|||
|
|
type: string
|
|||
|
|
details:
|
|||
|
|
type: array
|
|||
|
|
items:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
field:
|
|||
|
|
type: string
|
|||
|
|
message:
|
|||
|
|
type: string
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## OpenAPI 3.0 模板
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
openapi: 3.0.3
|
|||
|
|
info:
|
|||
|
|
title: {API 名稱}
|
|||
|
|
version: 1.0.0
|
|||
|
|
description: |
|
|||
|
|
{描述}
|
|||
|
|
|
|||
|
|
Related PRD: {PRD 連結}
|
|||
|
|
|
|||
|
|
servers:
|
|||
|
|
- url: https://api.example.com/v1
|
|||
|
|
description: Production
|
|||
|
|
- url: https://staging-api.example.com/v1
|
|||
|
|
description: Staging
|
|||
|
|
|
|||
|
|
paths:
|
|||
|
|
/users:
|
|||
|
|
get:
|
|||
|
|
summary: 列出使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
parameters:
|
|||
|
|
- name: page
|
|||
|
|
in: query
|
|||
|
|
schema:
|
|||
|
|
type: integer
|
|||
|
|
default: 1
|
|||
|
|
- name: limit
|
|||
|
|
in: query
|
|||
|
|
schema:
|
|||
|
|
type: integer
|
|||
|
|
default: 20
|
|||
|
|
maximum: 100
|
|||
|
|
- name: search
|
|||
|
|
in: query
|
|||
|
|
schema:
|
|||
|
|
type: string
|
|||
|
|
responses:
|
|||
|
|
'200':
|
|||
|
|
description: 成功
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UserListResponse'
|
|||
|
|
'401':
|
|||
|
|
$ref: '#/components/responses/Unauthorized'
|
|||
|
|
'500':
|
|||
|
|
$ref: '#/components/responses/InternalError'
|
|||
|
|
|
|||
|
|
post:
|
|||
|
|
summary: 建立使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
requestBody:
|
|||
|
|
required: true
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/CreateUserRequest'
|
|||
|
|
responses:
|
|||
|
|
'201':
|
|||
|
|
description: 建立成功
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UserResponse'
|
|||
|
|
'400':
|
|||
|
|
$ref: '#/components/responses/BadRequest'
|
|||
|
|
'422':
|
|||
|
|
$ref: '#/components/responses/ValidationError'
|
|||
|
|
|
|||
|
|
/users/{id}:
|
|||
|
|
parameters:
|
|||
|
|
- name: id
|
|||
|
|
in: path
|
|||
|
|
required: true
|
|||
|
|
schema:
|
|||
|
|
type: string
|
|||
|
|
|
|||
|
|
get:
|
|||
|
|
summary: 取得使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
responses:
|
|||
|
|
'200':
|
|||
|
|
description: 成功
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UserResponse'
|
|||
|
|
'404':
|
|||
|
|
$ref: '#/components/responses/NotFound'
|
|||
|
|
|
|||
|
|
put:
|
|||
|
|
summary: 完整更新使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
requestBody:
|
|||
|
|
required: true
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UpdateUserRequest'
|
|||
|
|
responses:
|
|||
|
|
'200':
|
|||
|
|
description: 更新成功
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UserResponse'
|
|||
|
|
'404':
|
|||
|
|
$ref: '#/components/responses/NotFound'
|
|||
|
|
'422':
|
|||
|
|
$ref: '#/components/responses/ValidationError'
|
|||
|
|
|
|||
|
|
patch:
|
|||
|
|
summary: 部分更新使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
requestBody:
|
|||
|
|
required: true
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/PatchUserRequest'
|
|||
|
|
responses:
|
|||
|
|
'200':
|
|||
|
|
description: 更新成功
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/UserResponse'
|
|||
|
|
'404':
|
|||
|
|
$ref: '#/components/responses/NotFound'
|
|||
|
|
|
|||
|
|
delete:
|
|||
|
|
summary: 刪除使用者
|
|||
|
|
tags:
|
|||
|
|
- Users
|
|||
|
|
responses:
|
|||
|
|
'204':
|
|||
|
|
description: 刪除成功
|
|||
|
|
'404':
|
|||
|
|
$ref: '#/components/responses/NotFound'
|
|||
|
|
|
|||
|
|
components:
|
|||
|
|
schemas:
|
|||
|
|
User:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
id:
|
|||
|
|
type: string
|
|||
|
|
example: "usr_123456"
|
|||
|
|
email:
|
|||
|
|
type: string
|
|||
|
|
format: email
|
|||
|
|
example: "user@example.com"
|
|||
|
|
name:
|
|||
|
|
type: string
|
|||
|
|
example: "John Doe"
|
|||
|
|
status:
|
|||
|
|
type: string
|
|||
|
|
enum: [active, inactive, suspended]
|
|||
|
|
example: "active"
|
|||
|
|
created_at:
|
|||
|
|
type: string
|
|||
|
|
format: date-time
|
|||
|
|
updated_at:
|
|||
|
|
type: string
|
|||
|
|
format: date-time
|
|||
|
|
required:
|
|||
|
|
- id
|
|||
|
|
- email
|
|||
|
|
- name
|
|||
|
|
- status
|
|||
|
|
- created_at
|
|||
|
|
- updated_at
|
|||
|
|
|
|||
|
|
CreateUserRequest:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
email:
|
|||
|
|
type: string
|
|||
|
|
format: email
|
|||
|
|
password:
|
|||
|
|
type: string
|
|||
|
|
minLength: 8
|
|||
|
|
name:
|
|||
|
|
type: string
|
|||
|
|
minLength: 1
|
|||
|
|
maxLength: 100
|
|||
|
|
required:
|
|||
|
|
- email
|
|||
|
|
- password
|
|||
|
|
- name
|
|||
|
|
|
|||
|
|
UpdateUserRequest:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
email:
|
|||
|
|
type: string
|
|||
|
|
format: email
|
|||
|
|
name:
|
|||
|
|
type: string
|
|||
|
|
minLength: 1
|
|||
|
|
maxLength: 100
|
|||
|
|
status:
|
|||
|
|
type: string
|
|||
|
|
enum: [active, inactive]
|
|||
|
|
|
|||
|
|
PatchUserRequest:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
name:
|
|||
|
|
type: string
|
|||
|
|
minLength: 1
|
|||
|
|
maxLength: 100
|
|||
|
|
status:
|
|||
|
|
type: string
|
|||
|
|
enum: [active, inactive]
|
|||
|
|
|
|||
|
|
UserResponse:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
data:
|
|||
|
|
$ref: '#/components/schemas/User'
|
|||
|
|
|
|||
|
|
UserListResponse:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
data:
|
|||
|
|
type: array
|
|||
|
|
items:
|
|||
|
|
$ref: '#/components/schemas/User'
|
|||
|
|
meta:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
page:
|
|||
|
|
type: integer
|
|||
|
|
limit:
|
|||
|
|
type: integer
|
|||
|
|
total:
|
|||
|
|
type: integer
|
|||
|
|
total_pages:
|
|||
|
|
type: integer
|
|||
|
|
|
|||
|
|
Error:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
error:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
code:
|
|||
|
|
type: string
|
|||
|
|
message:
|
|||
|
|
type: string
|
|||
|
|
details:
|
|||
|
|
type: array
|
|||
|
|
items:
|
|||
|
|
type: object
|
|||
|
|
properties:
|
|||
|
|
field:
|
|||
|
|
type: string
|
|||
|
|
message:
|
|||
|
|
type: string
|
|||
|
|
|
|||
|
|
responses:
|
|||
|
|
BadRequest:
|
|||
|
|
description: 請求格式錯誤
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/Error'
|
|||
|
|
|
|||
|
|
Unauthorized:
|
|||
|
|
description: 未認證
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/Error'
|
|||
|
|
|
|||
|
|
NotFound:
|
|||
|
|
description: 資源不存在
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/Error'
|
|||
|
|
|
|||
|
|
ValidationError:
|
|||
|
|
description: 驗證錯誤
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/Error'
|
|||
|
|
|
|||
|
|
InternalError:
|
|||
|
|
description: 伺服器內部錯誤
|
|||
|
|
content:
|
|||
|
|
application/json:
|
|||
|
|
schema:
|
|||
|
|
$ref: '#/components/schemas/Error'
|
|||
|
|
|
|||
|
|
securitySchemes:
|
|||
|
|
bearerAuth:
|
|||
|
|
type: http
|
|||
|
|
scheme: bearer
|
|||
|
|
bearerFormat: JWT
|
|||
|
|
|
|||
|
|
security:
|
|||
|
|
- bearerAuth: []
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 設計檢查清單
|
|||
|
|
|
|||
|
|
### 安全性
|
|||
|
|
- [ ] 所有端點都有適當的認證機制
|
|||
|
|
- [ ] 輸入驗證完整
|
|||
|
|
- [ ] 速率限制設計
|
|||
|
|
- [ ] CORS 配置
|
|||
|
|
|
|||
|
|
### 效能
|
|||
|
|
- [ ] 分頁支援
|
|||
|
|
- [ ] 快取策略
|
|||
|
|
- [ ] 批次操作支援
|
|||
|
|
|
|||
|
|
### 可靠性
|
|||
|
|
- [ ] 錯誤回應格式統一
|
|||
|
|
- [ ] 重試機制建議
|
|||
|
|
- [ ] 冪等性設計
|
|||
|
|
|
|||
|
|
### 一致性
|
|||
|
|
- [ ] 命名規範一致 (資源用名詞複數)
|
|||
|
|
- [ ] 回應格式一致 (data/meta/error 結構)
|
|||
|
|
- [ ] 篩選/排序/分頁參數一致
|
|||
|
|
|
|||
|
|
## 相依技能
|
|||
|
|
|
|||
|
|
- **前置**: `write-a-prd` (PRD 通過後)
|
|||
|
|
- **輔助**: `design-an-interface` (探索多種設計方案)
|
|||
|
|
- **後續**: `dba-schema` (DB Schema 設計)
|
|||
|
|
- **退回**: 可退回 `write-a-prd` 修改需求
|
|||
|
|
|
|||
|
|
## 退回機制
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Design Review 退回 API 設計
|
|||
|
|
↓
|
|||
|
|
修改 OpenAPI 規格
|
|||
|
|
↓
|
|||
|
|
重新提交 (或重新呼叫 design-an-interface)
|
|||
|
|
|
|||
|
|
DBA Agent 發現 Schema 衝突
|
|||
|
|
↓
|
|||
|
|
協商調整 domain model 或 API 回應格式
|
|||
|
|
↓
|
|||
|
|
更新 OpenAPI 規格
|
|||
|
|
```
|