iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
Build on AWS

AWS架構師的自我修養:30天雲端系統思維實戰指南系列 第 27

Day 13-2 | 跨團隊協作設計:技術文件、OpenAPI、共用契約 : API 文檔化與團隊協作標準建立 - 可文檔版控的視覺化商業邏輯具象(2):系統交互介面(OpenAPI)

  • 分享至 

  • xImage
  •  

2. 系統交互介面(OpenAPI)

抽象概念:API 的「標準化合約」

OpenAPI (Swagger) = API 的身分證 + 使用手冊 + 自動化工具
- 定義輸入格式 → 你需要提供什麼資料
- 定義輸出格式 → 你會得到什麼回應
- 定義錯誤處理 → 出錯時會發生什麼
- 自動生成文件 → 團隊協作的可視化界面
- 程式碼生成 → 減少重複工作

OpenAPI(前身為 Swagger)是現代 API 開發中最重要的標準之一。它不僅僅是一份文件,更是整個 API 生命週期的核心工具。想像一下,如果共用契約是建築的「材料規格書」,那麼 OpenAPI 就是完整的「建築藍圖」,詳細描述每個房間的佈局、門窗位置、電路配置等所有細節。

2.1 為什麼需要 OpenAPI?

在沒有 OpenAPI 之前,API 開發常常面臨以下困境:

情境重現:API 開發的混亂時代

後端工程師:「我做好 API 了!」
前端工程師:「要怎麼呼叫?參數是什麼?」
後端工程師:「你看一下程式碼就知道了...」
前端工程師:「我不會看後端程式碼啊!」
測試工程師:「我要怎麼測試?有文件嗎?」
產品經理:「這個 API 到底有什麼功能?」

這種場景是否似曾相識?OpenAPI 的出現就是為了解決這些溝通與協作問題。

2.2 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 擁有豐富的工具生態系統,可以自動化許多重複性工作:

  • 程式碼生成:自動生成前端 SDK、後端 stub 程式碼
  • 測試工具:自動生成 API 測試案例
  • 文件網站:自動產生美觀的互動式文件
  • 模擬服務:快速建立 Mock Server

2.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

2.4 進階 OpenAPI 特性

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

2.5 團隊協作中的 OpenAPI 最佳實務

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 可能會有變更。
        不建議在正式環境中使用。

2.6 OpenAPI 在實際團隊協作中的工作流程

情境:電商平台新增「商品評價系統」

讓我們透過一個實際案例來看 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

2.7 OpenAPI 最佳實務指南

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 欄位"

2.8 團隊協作實戰案例

案例背景:一個 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 自動生成:

  • API 文件網站:供前端團隊參考
  • SDK 程式碼:自動生成前端 API 呼叫程式碼
  • 測試報告:契約符合度測試結果
  • 部署文件:API 部署和監控設定

上一篇
Day 13-1 | 跨團隊協作設計:技術文件、OpenAPI、共用契約 : API 文檔化與團隊協作標準建立 - 可文檔版控的視覺化商業邏輯具象(1):共用契約 (Shared Contract)
系列文
AWS架構師的自我修養:30天雲端系統思維實戰指南27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言