> 九號工具站
返回列表

Microservices Contract Review — 跨服務 API 不一致的 8 個典型漏洞

微服務架構下 spec review 要看的東西。Consumer-Driven Contract Testing、Pact 工作流、跨服務 schema 漂移、版本兼容、事件驅動契約。

QA microservices contract-testing pact api-contract distributed-systems
· 最後更新 2026-06-13 · 最後審閱 2026-08-30

💡 本文原刊於 qa.9niche.com,2026-08 併入 9niche.com 懶人包,內容照原文完整搬遷。

1. 前言

微服務最大的痛不是寫不出來、是「服務 A 改了但 B 不知道」。Spec review 時除了單一 API、還要看跨服務的 contract 是不是會崩。這篇給你完整框架。

2. 為什麼微服務 spec 特別難 review

單體 review 看 1 個檔、微服務看 N × M 個介面

flowchart LR Mono[單體 app] --> M1[一個 spec / 一個 repo] Mono --> M2[編譯時抓不一致] Mono --> M3[一起發版] Micro[微服務] --> N1[N 個 spec / N 個 team] Micro --> N2[runtime 才抓錯] Micro --> N3[獨立發版] Micro --> N4[版本不同步] Micro --> N5[網路 + 序列化問題] style Mono fill:#10b981,color:#fff style Micro fill:#ef4444,color:#fff

3. 8 個典型漏洞

mindmap root((微服務 Contract<br>典型漏洞)) 1 schema 漂移 Provider 加欄位 Consumer 沒處理 null 2 enum 擴張 Provider 加新狀態 Consumer switch 沒 default 3 版本不同步 v1 / v2 並存 不同 consumer 用不同版 4 錯誤碼變 ERR_001 → SERVICE_UNAVAILABLE consumer 寫死字串 5 timeout 不一致 A 預設 5s B 預設 30s 鏈條 timeout 不對 6 retry 衝突 consumer + provider 都 retry 變雪崩 7 序列化版本 date format number precision enum string vs int 8 訊息順序 Async 訊息亂序 consumer 沒設 idempotency

4. 漏洞 1: Schema 漂移

Review 該問

  • 新增欄位有 nullable / default?
  • Consumer 端有 null check?
  • Schema 變更通知流程?
sequenceDiagram participant P as Provider Team participant C as Consumer Team P->>P: 加新欄位 `verified_at` (nullable) P->>P: 沒通知 C、直接 deploy C->>P: GET /users/123 P-->>C: {"id":123, "verified_at":null} C->>C: ❌ Cannot read property of null

5. 漏洞 2: Enum 擴張

// Provider 加新狀態
type OrderStatus = 'pending' | 'paid' | 'shipped' | 'cancelled' | 'refunded'; // ← 新加

// Consumer 寫法
switch (status) {
  case 'pending': return '處理中';
  case 'paid': return '已付款';
  case 'shipped': return '已出貨';
  case 'cancelled': return '已取消';
  // ❌ refunded 沒處理 → undefined
}

Review 該問

  • 新增 enum 有對齊 consumer 嗎?
  • Default case 處理?
  • Enum 拓展屬 breaking change?(不全是、但要分類)

6. 漏洞 3: 版本不同步

Service A (v1) ---calls---> Service B (v2)
                              ↑
                         v1 早已 deprecate
                         但 Service A 沒升

Review 該問

  • 版本 deprecation policy?
  • 多少 consumer 用 v1?
  • Migration 強制 deadline?

7. 漏洞 4: 錯誤碼變

Provider v1: error.code = "INSUFFICIENT_BALANCE"
Provider v2: error.code = "INSUFFICIENT_FUNDS"  ← 改了

Consumer:
if (err.code === "INSUFFICIENT_BALANCE") { /* 處理 */ }
// ❌ v2 後永遠跑不到這

Review 該問

  • 錯誤碼有 changelog 嗎?
  • 是否視為 breaking change?

8. 漏洞 5: Timeout 不一致

User → API Gateway (60s timeout)
       → Service A (10s)
         → Service B (30s)
           → DB (5s)

User 等了 60s 才看到錯誤、但 Service B 早超 A 的 timeout 了

Review 該問

  • 每層 timeout 是否 propagate?
  • 是否設 deadline 一致?
  • Retry 策略誰負責?

9. 漏洞 6: Retry 衝突

Review 該問

  • 哪一層 retry?
  • Idempotency key?
  • Exponential backoff?
sequenceDiagram participant C as Client participant A as Service A participant B as Service B C->>A: POST /order (with retry 3) A->>B: createOrder (with retry 3) Note over A,B: B 慢 — A timeout A->>B: retry 1 (createOrder) A->>B: retry 2 (createOrder) Note over C: client 也 timeout C->>A: retry 1 (POST /order) A->>B: createOrder × 3 again Note over B: 💥 訂單建了 6 次

10. 漏洞 7: 序列化版本

欄位ProviderConsumer 期待
created_at1734567890 (Unix sec)2026-06-13T10:00:00Z (ISO)
price9999.00
status"paid"1 (整數)

Review 該問

  • 日期一律 ISO 8601?
  • 數字 precision 規範?
  • Enum 是 string 還是 int?

11. 漏洞 8: 訊息順序(async)

Producer 順序送:
  msg1: order.created
  msg2: order.paid
  msg3: order.shipped

Consumer 收到:
  msg3 (網路快)
  msg1
  msg2

Consumer 處理 msg3 時 → 「order 不存在」

Review 該問

  • 訊息有 sequence number?
  • Consumer 有 idempotency?
  • Out-of-order 容錯機制?

12. Consumer-Driven Contract Testing (CDC)

Pact 工作流

// Consumer (寫期望)
import { Pact } from '@pact-foundation/pact';

const provider = new Pact({
  consumer: 'web-app',
  provider: 'user-service',
});

await provider.addInteraction({
  state: 'user 123 exists',
  uponReceiving: 'a request for user 123',
  withRequest: { method: 'GET', path: '/users/123' },
  willRespondWith: {
    status: 200,
    body: { id: 123, email: '[email protected]' },
  },
});
# Provider (verify 跑 pact file)
pact-verifier --provider-base-url http://localhost:8080 \
              --pact-url ./pacts/web-app-user-service.json

Provider 改 schema 不符合 → CI 直接擋

flowchart LR C[Consumer Team] -->|寫期望| Pact[Pact File<br>JSON] Pact --> P[Provider Team] P -->|跑 verify| Result{符合?} Result -->|是| Pass[✓ Build pass] Result -->|否| Fail[❌ Provider 知道<br>breaking change] style Pact fill:#06b6d4,color:#fff style Pass fill:#10b981,color:#fff style Fail fill:#ef4444,color:#fff

13. 微服務 Spec Review Checklist

mindmap root((微服務<br>Spec Review)) Contract 欄位增刪 Enum 擴張 Schema 嚴格度 Versioning Breaking change? Deprecation period Migration plan Error 錯誤碼穩定 4xx vs 5xx 分類 Error payload 結構 Timeout 每層 timeout Deadline propagate Circuit breaker Retry 策略誰負責 Idempotency Exponential backoff Async 訊息格式 順序保證 重複處理 Observability Trace ID 傳遞 每跳 log Metric 暴露 Auth Service-to-service Token TTL Permission 傳遞

14. QA 角度的工具

工具用途
PactREST + async contract test
SchemathesisOpenAPI fuzz testing
DreddOpenAPI 對 implementation 比對
GraphQL InspectorGraphQL schema diff
Postman Mocks模擬 provider
WireMock自架 mock server

15. 反模式

flowchart TD Anti[微服務 contract 反模式] --> A1["每次改 schema 不通知"] Anti --> A2["consumer 跟 provider 同 repo"] Anti --> A3["所有 retry 全做"] Anti --> A4["不寫 deprecation policy"] Anti --> A5["錯誤碼 freely 改"] Anti --> A6["timeout 各自設"] Anti --> A7["沒 trace id"] style A1 fill:#ef4444,color:#fff style A2 fill:#ef4444,color:#fff style A3 fill:#ef4444,color:#fff style A4 fill:#ef4444,color:#fff style A5 fill:#ef4444,color:#fff style A6 fill:#ef4444,color:#fff style A7 fill:#ef4444,color:#fff

16. 給 QA Lead 的 5 句

  1. 微服務的 bug 不在單一服務、在介面
  2. Contract test > Integration test 為主、Integration 為輔
  3. 沒 deprecation policy = 沒 versioning
  4. Retry 一條鏈只一個地方做
  5. Trace ID 不傳 = debug 不可能

17. 最後

微服務 spec review 是 QA 在分散式系統的主場。單一服務的 spec review 用既有 Spec Review Checklist、跨服務用這份。導入 Pact + 強制 deprecation policy + 統一 timeout / retry — 三個月後 production incident 砍 80%。

ℹ️

一般聲明

本站提供之資訊僅供參考,不保證其完整性與正確性。使用者應自行判斷資訊之適用性。

意見反饋