cover

先講結論

Sequence Diagram 是設計文件裡 CP 值最高的一種圖。它能清楚回答:「當使用者做 X 的時候,系統裡面到底發生了什麼?」

什麼時候畫:

  • 涉及 2 個以上服務的 API 呼叫流程
  • 跨前後端的關鍵業務流程(登入、付款、訂單)
  • 非同步訊息流(Kafka / RabbitMQ)的訊息傳遞順序
  • 需要讓 QA 理解測試場景的例外流程

什麼時候不用畫:

  • 只有一個服務、單純的 CRUD(直接看 API Spec 就夠了)
  • 流程邏輯很簡單、三行文字就能說清楚的

Mermaid 語法速查

Mermaid 是目前最推薦的 Sequence Diagram 工具,可以直接渲染在 GitHub、Notion、VS Code、GitLab。

基本宣告

sequenceDiagram
    actor User as 使用者          ← 人形圖示
    participant Browser as 瀏覽器  ← 方框(系統/服務)
    participant API as API Server
    participant DB as 資料庫

訊息類型

A->>B: 同步請求(實線箭頭)
B-->>A: 同步回應(虛線箭頭)
A-)B:  非同步訊息,fire and forget(實線開頭箭頭)
A--xB: 跨越 X,訊息被拒絕/失敗(少用)

生命線(Lifeline)

activate A     ← 啟動生命線(顯示 A 正在執行)
deactivate A   ← 結束生命線

流程控制

alt 條件一
    A->>B: 訊息
else 條件二
    A->>C: 另一個訊息
end

opt 選擇性操作(只有 if,沒有 else)
    A->>B: 訊息
end

loop 每 5 秒輪詢
    A->>B: polling
    B-->>A: 回應
end

備註

Note right of A: 右側備註
Note left of B: 左側備註
Note over A,B: 跨越 A 和 B 的備註

範本一:使用者登入

sequenceDiagram
    actor User as 使用者
    participant Browser as 瀏覽器
    participant API as API Server
    participant DB as 資料庫
    participant Redis as Redis

    User->>Browser: 輸入 Email + 密碼,點擊登入
    Browser->>API: POST /auth/login { email, password }
    activate API

    API->>DB: SELECT * FROM users WHERE email = ?
    activate DB
    DB-->>API: 回傳使用者資料
    deactivate DB

    alt 帳號不存在 或 密碼錯誤
        API->>DB: UPDATE users SET login_attempts = login_attempts + 1
        API-->>Browser: 401 { code: "INVALID_CREDENTIALS" }
    else 連續失敗 5 次
        API->>DB: UPDATE users SET locked_until = NOW() + 30min
        API-->>Browser: 429 { code: "ACCOUNT_LOCKED", retry_after: 1800 }
    else 帳號未驗證
        API-->>Browser: 403 { code: "EMAIL_NOT_VERIFIED" }
    else 登入成功
        API->>Redis: SET session:{token} userId, EX 1800
        API-->>Browser: 200 { token, expires_in: 1800, user }
        deactivate API
        Browser->>User: 導向首頁
    end

範本二:電商下訂單(跨服務)

sequenceDiagram
    actor User as 使用者
    participant Web as Web App
    participant OrderSvc as Order Service
    participant InventorySvc as Inventory Service
    participant PaymentSvc as Payment Service
    participant NotifySvc as Notification Service
    participant OrderDB as Order DB

    User->>Web: 點擊「確認下單」
    Web->>OrderSvc: POST /orders { items, userId }
    activate OrderSvc

    OrderSvc->>InventorySvc: POST /inventory/check { items }
    activate InventorySvc
    InventorySvc-->>OrderSvc: { available: true }
    deactivate InventorySvc

    alt 庫存不足
        OrderSvc-->>Web: 400 { code: "INSUFFICIENT_STOCK", details: { product_ids: [...] } }
        Web->>User: 顯示庫存不足商品
    else 庫存充足
        OrderSvc->>OrderDB: INSERT orders (status: pending)
        OrderSvc->>InventorySvc: POST /inventory/reserve { items, orderId }

        OrderSvc->>PaymentSvc: POST /payments { orderId, amount }
        activate PaymentSvc
        PaymentSvc-->>OrderSvc: { paymentId, status: success }
        deactivate PaymentSvc

        alt 付款失敗
            OrderSvc->>InventorySvc: POST /inventory/release { orderId }
            OrderSvc->>OrderDB: UPDATE orders SET status = failed
            OrderSvc-->>Web: 402 { code: "PAYMENT_FAILED" }
        else 付款成功
            OrderSvc->>OrderDB: UPDATE orders SET status = confirmed
            OrderSvc-)NotifySvc: POST /notify { userId, orderId, type: "order_confirmed" }
            Note right of NotifySvc: 非同步,不等回應
            OrderSvc-->>Web: 201 { orderId, status: confirmed }
            deactivate OrderSvc
            Web->>User: 顯示訂單確認頁
        end
    end

範本三:API Gateway + 微服務認證

sequenceDiagram
    actor Client as 客戶端
    participant Gateway as API Gateway
    participant AuthSvc as Auth Service
    participant UserSvc as User Service
    participant Cache as Redis Cache

    Client->>Gateway: GET /users/profile (Authorization: Bearer token)
    activate Gateway

    Gateway->>Cache: GET token:{token}
    activate Cache
    Cache-->>Gateway: 快取命中,回傳 userId
    deactivate Cache

    alt 快取未命中
        Gateway->>AuthSvc: POST /auth/verify { token }
        activate AuthSvc
        AuthSvc-->>Gateway: { valid: true, userId: 123, expires_at: ... }
        deactivate AuthSvc
        Gateway->>Cache: SET token:{token} userId EX 300
    end

    alt Token 無效
        Gateway-->>Client: 401 Unauthorized
    else Token 有效
        Gateway->>UserSvc: GET /users/123
        activate UserSvc
        UserSvc-->>Gateway: { id: 123, name: "...", email: "..." }
        deactivate UserSvc
        Gateway-->>Client: 200 { user profile }
        deactivate Gateway
    end

範本四:Webhook 接收與非同步處理

sequenceDiagram
    participant Stripe as Stripe(第三方)
    participant API as API Server
    participant Queue as Message Queue
    participant Worker as Background Worker
    participant DB as 資料庫
    participant NotifySvc as Notification Service

    Stripe->>API: POST /webhooks/stripe { event: "payment_intent.succeeded", ... }
    activate API

    Note over API: 驗證 HMAC 簽名
    alt 簽名驗證失敗
        API-->>Stripe: 400 Bad Request
    else 簽名驗證成功
        API->>Queue: PUBLISH payment.completed { orderId, amount }
        API-->>Stripe: 200 OK
        deactivate API
        Note over API,Stripe: 立即回 200,不讓 Stripe 逾時重試
    end

    Queue->>Worker: CONSUME payment.completed
    activate Worker
    Worker->>DB: UPDATE orders SET status = paid, paid_at = NOW()
    Worker->>DB: INSERT payments { orderId, amount, stripePaymentId }
    Worker->>NotifySvc: POST /notify { userId, type: "payment_confirmed" }
    Worker-->>Queue: ACK
    deactivate Worker

範本五:Token 刷新流程

sequenceDiagram
    actor User as 使用者
    participant App as 前端 App
    participant API as API Server
    participant DB as 資料庫

    User->>App: 操作任意功能
    App->>API: GET /protected-resource (Access Token 過期)
    API-->>App: 401 { code: "TOKEN_EXPIRED" }

    App->>API: POST /auth/refresh { refresh_token }
    activate API

    API->>DB: SELECT * FROM refresh_tokens WHERE token = ? AND expires_at > NOW()
    activate DB
    DB-->>API: 回傳 Token 記錄
    deactivate DB

    alt Refresh Token 無效或已過期
        API-->>App: 401 { code: "REFRESH_TOKEN_EXPIRED" }
        App->>User: 導向登入頁
    else Refresh Token 有效
        API->>DB: 撤銷舊 Refresh Token
        API->>DB: 新增新 Refresh Token
        API-->>App: 200 { access_token, refresh_token, expires_in }
        deactivate API
        App->>API: 重試原始請求(帶新 Access Token)
        API-->>App: 200 { 原始資源 }
        App->>User: 正常顯示
    end

常見模式備忘

非同步 vs 同步

sequenceDiagram
    participant A
    participant B

     非同步(fire and forget,不等回應)
    A-)B: 非同步訊息

    %% 輪詢
    loop 每 5 秒
        A->>B: 狀態查詢
        B-->>A: 回應
    end

選擇性操作

sequenceDiagram
    participant A
    participant B

    opt 使用者有開啟通知
        A->>B: 發送推播
        B-->>A: 推播成功
    end

畫圖原則

一張圖只說一件事。不要把所有流程塞進一張圖,分開畫,用文字說明流程間的關係。

例外流程要畫,但要分開。主要流程和例外流程可以合在一張圖(用 alt),但如果例外流程複雜,可以拆出單獨的圖說明。

命名要用業務語言。服務名稱叫 Order Service 而不是 src/services/orderService.ts;事件叫「付款成功」而不是 payment_intent.succeeded

標注非同步邊界。什麼是同步等待、什麼是非同步 fire and forget,要在圖上或備註裡說清楚。


相關文章