在經歷第一階段商業邏輯的素描後,我們大致知道了我們的系統與他的系統邊界,為了讓不同背景的團隊成員能夠高效協作,我們需要有一個實體的、非抽象化的文件檔作為依據。就像樂團演奏時需要樂譜一樣,軟體開發團隊需要清晰的技術規範來確保和諧協作。想像一下,我們正在建造一座大型購物中心,需要建築師、電工、水管工、室內設計師等多個專業團隊協作,如果沒有統一的藍圖和溝通標準,每個團隊都按自己的理解施工,最終會是一場災難。
在軟體開發中也是如此:
接下來就可以將實際業務情境具象化成文檔協助我們在開發流程中不斷地比照路徑圖確認自己並沒有跑偏。大致上的參考順序會是:
共用契約 (Shared Contract) => 系統交互介面(OpenAPI) => 技術文件 (Technical Documentation)
以下將按照順序分別說明應用情境與範例
簡單來說,共用契約就像是建築藍圖中的「IPLC 電路規格」或「國際 PVC-U 水管尺寸標準」。它有著最基礎的規格依據讓負責不同部分的工班(開發團隊)可以獨立施工,確保最終所有零件都能完美地組裝在一起,從而避免溝通不良和整合時的混亂。常見的約定層面有 : 資料格式約定 、 通訊協定約定 、 錯誤處理約定。
例如:
respond
物件必須包含 isSuccess
、 element
、 errorMessage
... 等欄位以及它們的資料型別。這個共同的認知文件是一份跨團隊的共同語言,幾乎所有參與產品開發的技術和產品團隊都會以不同方式使用到它。
前端團隊 (Frontend Team) / **行動應用團隊 (Mobile App Team) ** 可以使用契約產生 Mock Data (模擬資料),在後端 API 還沒完成時也能獨立開發和測試,避免在整合時才發現「後端給的資料格式跟我想的不一樣」。契約告訴他們可以向後端請求什麼資料、需要用什麼格式發送請求,以及會收到什麼格式的回應,接下來才能根據契約定義的資料結構來開發 UI 介面。
後端團隊 (Backend Team) 是 API 的「提供者」,契約是他們需要履行的承諾與規格書。一旦出現差異,所有團隊包括且不限於前端 、其它後端(AI 專長或是圖像專長)、測試、產品與 DevOps 全部都會受到影響,所以作為 API 開發的明確指引,確保提供的資料格式、路徑、錯誤碼都符合約定。
測試團隊 (Test Team / QA) 需要根據契約中的請求/回應格式、HTTP 狀態碼和錯誤定義,來撰寫自動化測試驗證標準(例如:smoking test)驗證 API 的實際行為是否與契約描述的完全一致 - 契約是撰寫測試案例的黃金標準。
產品團隊 (Product Team / PM) 是需求的「定義者」,契約文件幫助他們確認技術實現是否符合業務需求。雖然他們不看程式碼,但可以透過 OpenAPI (Swagger) 文件這種視覺化的契約來了解 API 功能並確認 API 提供的欄位是否滿足前端畫面的需求,避免功能遺漏。
DevOps / SRE 團隊 契約文件能夠幫助他們理解系統間的互動,了解服務之間的通訊協定 (HTTP/gRPC),以配置正確的網路規則和監控,特別是在發生問題時,可以根據契約快速定位是哪個服務的溝通環節出了問題。
openapi: 3.0.3
info:
title: Shared Contract Library
description: Common reusable components for API contracts
version: 1.0.0
components:
# =================
# Common Parameters
# =================
parameters:
PageNumber:
name: page
in: query
description: Page number
required: true
schema:
type: integer
minimum: 1
PageSize:
name: pageSize
in: query
description: Number of items per page
required: true
schema:
type: integer
minimum: 1
maximum: 100
SortColumn:
name: sortColumn
in: query
description: Sort column
required: false
schema:
type: string
default: id
SortOrder:
name: orderType
in: query
description: Sort order
required: false
schema:
type: integer
format: int32
enum:
- 0 # Ascending
- 1 # Descending
default: 0
StartDate:
name: startDate
in: query
description: Start date filter
required: false
schema:
type: string
format: date
EndDate:
name: endDate
in: query
description: End date filter
required: false
schema:
type: string
format: date
EntityId:
name: id
in: path
description: Entity ID
required: true
schema:
type: string
# =================
# Common Responses
# =================
responses:
Success:
description: Operation successful
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
Created:
description: Resource created successfully
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- type: object
properties:
id:
type: integer
description: The ID of the newly created resource
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
BadRequest:
description: Invalid input
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
ValidationError:
description: Validation failed
content:
application/json:
schema:
$ref: "#/components/schemas/ValidationErrorResponse"
# =================
# Common Schemas
# =================
schemas:
# Base Response Structure
BaseResponse:
type: object
required:
- isSuccess
- message
properties:
isSuccess:
type: boolean
description: Indicates if the operation was successful
message:
type: string
description: Response message
errors:
type: object
nullable: true
default: null
description: Error details if any
# Error Response
ErrorResponse:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- type: object
properties:
isSuccess:
enum: [false]
# Validation Error Response
ValidationErrorResponse:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- type: object
properties:
isSuccess:
enum: [false]
errors:
type: object
additionalProperties:
type: array
items:
type: string
# Common Status Enum
EntityStatus:
type: integer
format: int32
enum:
- 0 # Draft/Template
- 1 # Pending Review
- 2 # Approved/Active
- 3 # Rejected
- 4 # Processing/Executing
- 5 # Completed/Executed
- 6 # Disabled/Inactive
description: |
Standard entity status codes:
- 0: Draft/Template
- 1: Pending Review
- 2: Approved/Active
- 3: Rejected
- 4: Processing/Executing
- 5: Completed/Executed
- 6: Disabled/Inactive
# Date Range Filter
DateRangeFilter:
type: object
properties:
startDate:
type: string
format: date
description: Filter start date
endDate:
type: string
format: date
description: Filter end date
# Basic Entity Properties
BaseEntity:
type: object
required:
- id
properties:
id:
type: integer
description: Unique identifier
status:
$ref: "#/components/schemas/EntityStatus"
# =================
# Common Examples
# =================
examples:
SuccessResponse:
summary: Successful operation
value:
isSuccess: true
message: "Operation completed successfully"
errors: null
ErrorResponse:
summary: Error response
value:
isSuccess: false
message: "Operation failed"
errors:
general: ["An error occurred"]
PaginatedResponse:
summary: Paginated list response
value:
isSuccess: true
message: "Data retrieved successfully"
errors: null
result:
page: 1
totalCount: 100
datas: []
SelectOptions:
summary: Select options list
value:
isSuccess: true
message: "Options retrieved successfully"
errors: null
element:
- id: "0"
text: "Active"
disabled: false
- id: "1"
text: "Inactive"
disabled: false
# 共用契約範例:平台 API v1
**文件目的**:此契約定義了電商平台 API v1 的通用規範與核心端點 (`Product`) 的互動方式,作為前端、後端、測試與產品團隊的共同協作依據。
---
### 1. 通訊協定約定 (Communication Protocol)
- **協定**: 所有 API 均透過 `HTTPS` 提供服務。
- **基礎路徑 (Base URL)**: `https://api.your-ecommerce.com/v1`
- **認證 (Authentication)**: 所有需要授權的請求,都必須在 HTTP Header 中帶上 `Authorization` 欄位,其值為 `Bearer <YOUR_API_TOKEN>`。
- **請求與回應格式**: 所有請求與回應的 `body` 均使用 `application/json` 格式。
---
### 2. 標準回應格式與錯誤處理約定 (Standard Response & Error Handling)
為了讓所有客戶端(前端、APP)能用統一的方式處理 API 回應,我們定義一個標準的回應包裝 (Response Wrapper)。
#### 2.1. 標準回應結構 (Standard Response Schema)
所有 API 回應都必須遵循以下結構。我們可以使用 TypeScript Interface 來清晰地定義它:
/\*\*
- 標準 API 回應的共用契約
\*/
interface ApiResponse<T> {
/\*\*
- 請求是否成功
\*/
success: boolean;
/\*\*
- 成功時的回應資料 (泛型 T)
- 若請求失敗,此欄位為 null
\*/
data: T | null;
/\*\*
- 失敗時的錯誤資訊物件
- 若請求成功,此欄位為 null
\*/
error: ApiError | null;
}
/\*\*
- 標準錯誤物件結構
\*/
interface ApiError {
/\*\*
- 內部定義的錯誤代碼,方便前端進行邏輯判斷
\*/
code: number;
/\*\*
- 人類可讀的錯誤訊息
\*/
message: string;
}
#### 2.2. 通用錯誤代碼 (Common Error Codes)
| HTTP 狀態碼 | 內部代碼 (`code`) | 說明 |
| :---------- | :---------------- | :------------------------------------- |
| `400` | `40001` | 請求參數驗證失敗 (Invalid Parameters) |
| `401` | `40101` | 未經授權 (Unauthorized) |
| `403` | `40301` | 權限不足 (Forbidden) |
| `404` | `40401` | 請求的資源不存在 (Resource Not Found) |
| `500` | `50000` | 伺服器內部錯誤 (Internal Server Error) |
---
### 3. 具體端點契約:取得商品資訊
現在,我們將上述通用約定應用到一個具體的端點上。
**端點**: `GET /products/{productId}`
**描述**: 根據提供的 `productId` 取得單一商品的詳細資訊。
#### 3.1. 請求 (Request)
- **路徑參數 (Path Parameter)**:
- `productId` (string, format: uuid): 商品的唯一識別碼。
#### 3.2. 回應 (Responses)
- **資料格式約定 (Data Schema)**: 首先定義 `Product` 物件的契約。
// 商品物件的共用契約
interface Product {
id: string; // UUID
name: string;
description: string;
price: number;
currency: 'TWD' | 'USD';
stock: number;
imageUrl: string;
createdAt: string; // ISO 8601 format date string
}
- **成功回應 (200 OK)**:
當商品成功找到時,HTTP 狀態碼為 `200`,回應 `body` 遵循 `ApiResponse<Product>` 結構。
json
// Response Body (200 OK)
{
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"name": "高效能無線機械鍵盤",
"description": "提供極致的打字體驗與 RGB 燈效。",
"price": 3200,
"currency": "TWD",
"stock": 150,
"imageUrl": "https://cdn.your-ecommerce.com/images/keyboard.jpg",
"createdAt": "2025-09-18T10:00:00Z"
},
"error": null
}
- **失敗回應 (404 Not Found)**:
當 `productId` 對應的商品不存在時,HTTP 狀態碼為 `404`,回應 `body` 遵循 `ApiResponse<null>` 結構。
json
// Response Body (404 Not Found)
{
"success": false,
"data": null,
"error": {
"code": 40401,
"message": "商品不存在"
}
}
- **失敗回應 (400 Bad Request)**:
當 `productId` 格式不正確(不是有效的 UUID)時,HTTP 狀態碼為 `400`。
json
// Response Body (400 Bad Request)
{
"success": false,
"data": null,
"error": {
"code": 40001,
"message": "請求參數驗證失敗: productId 必須是有效的 UUID 格式"
}
}
---
### 如何使用這份契約
- **後端團隊**:以此為規格書,實作 `GET /products/{productId}` 端點,確保回傳的 JSON 結構完全符合契約。
- **前端團隊**:在後端還在開發時,就可以根據這份契約建立 `Product` 的 TypeScript 型別,並使用 Mock Server 模擬成功和失敗的回應來開發商品詳情頁面。
- **測試團隊**:撰寫自動化測試案例,分別驗證 200, 404, 400 等情境下的回應是否與契約一致。