【系統設計 20 概念】#02 API 通訊模式 — REST、GraphQL、gRPC、WebSockets

測驗:API 通訊模式 — REST、GraphQL、gRPC、WebSockets

共 5 題,點選答案後會立即顯示結果

1. REST API 的「無狀態(Stateless)」原則是什麼意思?

  • A. 伺服器不儲存任何資料,所有資料都在客戶端
  • B. 每次請求都必須使用不同的 URL
  • C. 每次請求都要帶齊所有資訊,伺服器不記得你上次做了什麼
  • D. API 不能使用 POST 或 PUT 方法來修改資料

2. GraphQL 主要解決了 REST 的哪兩個問題?

  • A. 延遲過高(Latency)和安全性不足(Insecurity)
  • B. 回傳過多不需要的資料(Over-fetching)和一次請求不夠用(Under-fetching)
  • C. 不支援二進位傳輸和缺少型別定義
  • D. 無法處理即時通訊和雙向資料傳輸

3. gRPC 比 REST 效能更好的關鍵原因是什麼?

  • A. gRPC 使用更快的程式語言來處理請求
  • B. gRPC 不需要經過網路傳輸,直接在記憶體中通訊
  • C. gRPC 自動做了資料快取,減少重複計算
  • D. gRPC 使用 Protocol Buffers 傳輸二進位資料,體積更小、解析更快,且基於 HTTP/2

4. 為什麼聊天室適合使用 WebSockets 而不是 HTTP 輪詢(Polling)?

  • A. HTTP 輪詢無法傳送文字訊息,只能傳送圖片
  • B. WebSockets 的資料加密比 HTTP 更安全
  • C. HTTP 輪詢需要不斷發送請求浪費資源,WebSockets 建立持續連線後雙方可隨時主動發訊息
  • D. WebSockets 使用 Protocol Buffers,傳輸速度比 HTTP 快十倍

5. 你正在設計一個系統,包含:對外的商品查詢 API、內部微服務間的高頻通訊、以及即時客服聊天功能。根據文章的決策流程,最合適的組合是?

  • A. 全部使用 GraphQL,因為它最彈性
  • B. 對外用 REST、內部用 gRPC、聊天用 WebSockets
  • C. 對外用 gRPC、內部用 REST、聊天用 GraphQL
  • D. 全部使用 REST,因為它最通用且學習成本最低

本篇是「系統設計 20 概念」系列的第 2 篇,共 5 篇。
難度:L2-進階 | 前置知識:第 1 篇的 HTTP 基礎知識
參考來源:20 System Design Concepts Explained in 10 Minutes (NeetCode, YouTube)

一句話說明

四種 API 通訊模式,決定了前後端或服務之間「用什麼方式對話」。


為什麼需要不同的通訊模式?

上一篇我們學了 HTTP 的基本運作:客戶端發 Request,伺服器回 Response。但真實世界的需求五花八門:

  • 購物網站只需要查商品、下訂單 — REST 就夠了
  • 社交 App 首頁要組合使用者資料、貼文、好友列表 — GraphQL 更省事
  • 微服務之間每秒互打幾萬次呼叫 — gRPC 效能最好
  • 聊天室需要即時推播訊息 — WebSockets 才做得到

選錯模式不會讓系統壞掉,但會讓你多寫很多不必要的程式碼,或者效能差到被使用者罵。


REST:最常見的 API 風格

這在幹嘛

REST(Representational State Transfer)把伺服器上的東西都當作「資源」,每個資源有固定的網址,用 HTTP 方法(GET、POST、PUT、DELETE)來操作。

最小範例

GET    /users          → 取得所有使用者
GET    /users/123      → 取得 ID 為 123 的使用者
POST   /users          → 新增一個使用者
PUT    /users/123      → 更新 ID 為 123 的使用者
DELETE /users/123      → 刪除 ID 為 123 的使用者

翻譯:就像圖書館的書架,每本書(資源)都有固定的編號(URL),你可以查閱(GET)、登記新書(POST)、更換內容(PUT)、下架(DELETE)。

AI 生成的程式碼裡常看到這樣

@app.get("/users/{user_id}")      # 定義一個 GET 端點,{user_id} 是動態參數
async def get_user(user_id: int):  # 從網址抓出 user_id
    user = await db.find(user_id)  # 去資料庫查
    return user                    # 回傳 JSON
Code language: PHP (php)

逐行翻譯

  • @app.get("/users/{user_id}") — 當有人用 GET 訪問 /users/123 時,執行下面這個函式
  • user_id: int — 把網址中的 123 抓出來,型別是整數
  • await db.find(user_id) — 非同步地去資料庫找資料
  • return user — 把結果回傳,框架會自動轉成 JSON

REST 的核心原則

原則 白話解釋
無狀態(Stateless) 每次請求都要帶齊所有資訊,伺服器不記得你上次做了什麼
統一介面(Uniform Interface) 所有資源都用同一套 URL + HTTP 方法的規則
資源導向(Resource-based) 一切都是「名詞」:/users、/orders,不是「動詞」

適用場景

  • CRUD 操作(增刪改查)
  • 公開 API(第三方串接)
  • 大部分的 Web 和 Mobile App

GraphQL:客戶端說了算要什麼資料

這在幹嘛

GraphQL 讓客戶端「點菜」– 你告訴伺服器你要哪些欄位,伺服器只回傳那些欄位。不多不少。

REST 的痛點

假設你在做一個社交 App 的個人頁面,需要:使用者名稱、最近 5 篇貼文、好友數量。

用 REST 你可能要打三次 API:

GET /users/123           → 拿使用者資料(但包含一堆你不需要的欄位)
GET /users/123/posts     → 拿貼文列表
GET /users/123/friends   → 拿好友列表

Over-fetching:每個 API 回傳太多你不需要的欄位(例如使用者的地址、生日)。 Under-fetching:一個 API 不夠用,要打好幾次才能湊齊資料。

GraphQL 怎麼解決

query {
  user(id: 123) {        # 我要 ID 123 的使用者
    name                  # 只要名字
    posts(last: 5) {      # 最近 5 篇貼文
      title               # 只要標題
    }
    friendCount           # 好友數量
  }
}
Code language: PHP (php)

翻譯:一次請求,精確告訴伺服器「我要什麼」,伺服器只回傳你要的東西。

回傳結果:

{
  "data": {
    "user": {
      "name": "Alice",
      "posts": [
        { "title": "第一篇文章" },
        { "title": "第二篇文章" }
      ],
      "friendCount": 42
    }
  }
}
Code language: JSON / JSON with Comments (json)

必看懂 vs 知道就好

必看懂:
- query:查詢資料(對應 REST 的 GET)
- mutation:修改資料(對應 REST 的 POST/PUT/DELETE)
- 巢狀結構:可以在一次查詢中取得關聯資料

知道就好:
- subscription:即時訂閱資料變化
- schema:定義有哪些資料可以查
- resolver:伺服器端處理查詢的函式

適用場景

  • 前端需要彈性組合資料的 App(社交平台、Dashboard)
  • 行動裝置(省流量,只拿需要的欄位)
  • 多種客戶端(Web、iOS、Android 各自需要不同欄位)

gRPC:微服務之間的高速公路

這在幹嘛

gRPC 是 Google 開發的高效能通訊框架。服務之間不傳 JSON 文字,而是傳壓縮過的二進位資料(Protocol Buffers),速度快非常多。

跟 REST 的關鍵差異

REST:
客戶端 → 發送 JSON 文字 → 伺服器
         {"name": "Alice", "age": 30}
         人看得懂,但檔案大、解析慢

gRPC:
客戶端 → 發送二進位資料 → 伺服器
         0A 05 41 6C 69 63 65 10 1E
         人看不懂,但檔案小、解析快
Code language: JavaScript (javascript)

最小範例:定義服務

gRPC 的起點是一個 .proto 檔案,定義好「有哪些服務」和「資料長什麼樣」:

// user.proto -- 定義 UserService 的合約

service UserService {              // 定義一個服務叫 UserService
  rpc GetUser (UserRequest)        // 提供一個方法叫 GetUser
    returns (UserResponse);        // 輸入 UserRequest,回傳 UserResponse
}

message UserRequest {              // 定義輸入的資料結構
  int32 user_id = 1;              // 欄位 1:使用者 ID(整數)
}

message UserResponse {             // 定義回傳的資料結構
  string name = 1;                // 欄位 1:名字(字串)
  int32 age = 2;                  // 欄位 2:年齡(整數)
}
Code language: JavaScript (javascript)

翻譯:這個 .proto 檔案就像一份合約,告訴雙方「我能提供什麼服務、你要送什麼格式、我會回什麼格式」。工具會根據這份合約自動生成程式碼。

為什麼比 REST 快?

比較項目 REST (JSON) gRPC (Protobuf)
資料格式 文字(人看得懂) 二進位(機器看得懂)
傳輸大小 較大 小 3-10 倍
解析速度 較慢 快 5-10 倍
HTTP 版本 HTTP/1.1 HTTP/2(支援多工)

適用場景

  • 微服務之間的內部通訊(不需要人看懂)
  • 對延遲敏感的系統(支付、交易)
  • 多語言環境(一份 .proto 自動生成 Go、Java、Python 等程式碼)

不適合的場景

  • 瀏覽器直接呼叫(瀏覽器原生不支援 gRPC)
  • 需要人工除錯的 API(二進位資料不好讀)
  • 對外公開的 API(第三方不一定會用 gRPC 工具)

WebSockets:全雙工即時通訊

這在幹嘛

WebSockets 讓客戶端和伺服器之間保持一條「持續連線」,雙方隨時都可以主動發送訊息,不需要一問一答。

HTTP 輪詢 vs WebSockets

假設你在做一個聊天室,想即時收到新訊息:

方法 1:HTTP 輪詢(Polling)– 每隔幾秒問一次

客戶端:有新訊息嗎?  → 伺服器:沒有
客戶端:有新訊息嗎?  → 伺服器:沒有
客戶端:有新訊息嗎?  → 伺服器:有!"Hello"
客戶端:有新訊息嗎?  → 伺服器:沒有
Code language: JavaScript (javascript)

問題:浪費大量請求,大部分時候都是「沒有」。

方法 2:WebSockets — 建立持續連線

客戶端:我要升級為 WebSocket 連線
伺服器:好,連線建立

(之後雙方隨時可以發訊息)
伺服器 → 客戶端:"Hello"      ← 伺服器主動推送
客戶端 → 伺服器:"Hi there!"  ← 客戶端也能隨時發
伺服器 → 客戶端:"How are you?"
Code language: JavaScript (javascript)

最小範例

// 建立 WebSocket 連線
const ws = new WebSocket("wss://chat.example.com");

ws.onopen = () => {            // 連線成功時
  ws.send("Hello!");           // 傳送訊息給伺服器
};

ws.onmessage = (event) => {   // 收到伺服器訊息時
  console.log(event.data);    // 印出訊息內容
};

ws.onclose = () => {           // 連線關閉時
  console.log("斷線了");
};
Code language: JavaScript (javascript)

逐行翻譯

  • new WebSocket("wss://...") — 建立一條 WebSocket 連線,wss 是加密版本(類似 HTTPS)
  • ws.onopen — 連線成功後要做什麼
  • ws.send(...) — 傳資料給伺服器
  • ws.onmessage — 伺服器推訊息過來時要做什麼
  • ws.onclose — 連線斷掉時要做什麼

適用場景

  • 即時聊天(LINE、Slack)
  • 股票報價、即時儀表板
  • 多人線上遊戲
  • 協作編輯(Google Docs)

不適合的場景

  • 單純的 CRUD 操作(用 REST 就好,不需要維持連線)
  • 低頻率的資料更新(每分鐘更新一次用輪詢就夠了)

四種模式總比較

REST GraphQL gRPC WebSockets
一句話 資源導向的標準 API 客戶端指定要什麼資料 高速二進位 RPC 雙向即時連線
資料格式 JSON JSON Protocol Buffers 任意(通常 JSON)
通訊方向 客戶端發起 客戶端發起 客戶端發起 雙向
典型場景 CRUD、公開 API 社交 App、Dashboard 微服務間通訊 聊天、即時報價
學習成本 中高
瀏覽器支援 原生支援 需要函式庫 需要 proxy 原生支援
適合對外 非常適合 適合 不太適合 看場景

系統設計面試:如何選擇通訊模式?

面試中被問到「這個系統應該用什麼 API」時,可以用這個決策流程:

需要即時雙向通訊嗎?(聊天、遊戲、即時通知)
  → 是 → WebSockets

是微服務之間的內部通訊嗎?
  → 是 → gRPC(效能優先)

客戶端需要彈性組合多種資料嗎?
  → 是 → GraphQL

以上都不是?
  → REST(最通用、最簡單)

實際案例對照

系統 推薦方案 原因
電商網站 REST 標準 CRUD:商品列表、購物車、訂單
Facebook/Instagram GraphQL 首頁要組合貼文、按讚數、留言、推薦好友
Uber 內部微服務 gRPC 定位、計價、派車,微服務之間高頻通訊
Slack 聊天 WebSockets 即時收發訊息,需要雙向通訊
YouTube 混合使用 REST(影片清單)+ WebSockets(即時聊天室)

重點提醒:真實系統通常不會只用一種模式。一個大型系統可能同時使用 REST 對外、gRPC 對內、WebSockets 做即時功能。


Vibe Coder 檢查點

當你在 AI 生成的程式碼中看到 API 通訊相關的程式碼時,確認以下事項:

  • [ ] 看到 @app.get@app.post 之類的裝飾器 — 這是 REST API,確認 HTTP 方法和 URL 路徑是否合理
  • [ ] 看到 query {mutation { — 這是 GraphQL,確認查詢的欄位是否是你需要的
  • [ ] 看到 .proto 檔案或 rpc 關鍵字 — 這是 gRPC,確認服務定義和資料結構是否正確
  • [ ] 看到 new WebSocketws.onmessage — 這是 WebSocket,確認有處理斷線重連的情況
  • [ ] 選擇通訊模式時 — 先問自己:需要即時嗎?是內部通訊嗎?需要彈性查詢嗎?

本篇重點回顧

  1. REST 是最通用的選擇,基於 HTTP 的資源導向設計,適合大部分 CRUD 場景
  2. GraphQL 解決了 REST 的 over-fetching/under-fetching 問題,讓客戶端精確指定要什麼資料
  3. gRPC 使用 Protocol Buffers 傳輸二進位資料,適合微服務間的高效能通訊
  4. WebSockets 建立持續的雙向連線,適合即時通訊場景
  5. 選擇通訊模式的核心問題:即時?內部?彈性查詢?都不是就用 REST

下一篇我們將探討「資料庫」– 系統設計中最重要的資料儲存層。

進階測驗:API 通訊模式 — REST、GraphQL、gRPC、WebSockets

測驗目標:驗證你是否能在實際情境中應用所學,正確選擇 API 通訊模式。
共 5 題,包含情境題與錯誤診斷題。

1. 你正在設計一個旅遊 App,首頁需要同時顯示:使用者的個人資料(頭像、暱稱)、最近瀏覽的 3 個景點、收藏的飯店列表、以及好友推薦。目前用 REST 實作,前端需要分別呼叫 4 個不同的端點才能湊齊資料,而且每個端點都回傳了很多用不到的欄位。最適合的改善方案是? 情境題

  • A. 改用 WebSockets,建立持續連線後一次取得所有資料
  • B. 改用 gRPC,用 Protocol Buffers 加速傳輸
  • C. 改用 GraphQL,讓前端在一次查詢中精確指定需要的欄位和關聯資料
  • D. 保持 REST 但建立一個新的 /homepage 端點,把所有資料合併回傳

2. 你的團隊正在建立一個多人協作白板應用。使用者可以同時在畫布上畫圖、移動物件,所有人的操作需要即時同步到其他人的畫面上。你應該選擇哪種通訊模式作為主要的即時同步機制? 情境題

  • A. REST — 每秒輪詢一次取得最新狀態
  • B. WebSockets — 建立持續雙向連線,任何人的操作都能即時推送給所有人
  • C. GraphQL subscription — 訂閱畫布變更事件
  • D. gRPC — 用二進位傳輸提高畫布座標資料的傳輸效率

3. 你的公司有一個電商平台,後端拆成了多個微服務:商品服務、訂單服務、支付服務、庫存服務。這些服務之間每秒需要互相呼叫數萬次,對延遲非常敏感(尤其是支付流程)。這些服務用 Go、Java、Python 不同語言撰寫。內部服務間通訊最適合用什麼? 情境題

  • A. REST — 最通用,所有語言都支援 HTTP
  • B. GraphQL — 讓每個服務精確指定需要的資料
  • C. WebSockets — 建立持續連線減少重複建立連線的開銷
  • D. gRPC — 高效能二進位傳輸,一份 .proto 可自動生成多語言程式碼

4. 小明在系統設計面試中,被要求設計一個即時股票報價系統。他的回答如下。請問他的設計有什麼問題? 錯誤診斷

面試官:設計一個即時股票報價系統,使用者打開 App 就能看到股價即時跳動。 小明的回答: 「我會用 REST API 來實作。前端每 100 毫秒發一次 GET /stocks/TSLA 來取得最新股價,這樣使用者就能看到近乎即時的更新。」
  • A. 問題在於 REST 無法傳輸數字類型的資料,應該用 gRPC
  • B. 每 100ms 輪詢一次會產生大量無意義的請求,應該用 WebSockets 讓伺服器在股價變動時主動推送
  • C. 問題在於 REST 的無狀態原則不允許頻繁請求同一個資源
  • D. 問題在於 GET 方法只能取得靜態資料,應該用 POST 來取得即時資料

5. 團隊新人小華負責設計對外公開的第三方開發者 API,讓外部公司串接你們的服務。他提交了以下設計方案。請問這個方案的主要問題是什麼? 錯誤診斷

小華的方案: 「我打算用 gRPC 來建立公開 API,因為 gRPC 效能比 REST 好很多。 第三方開發者需要安裝 gRPC 工具鏈,使用我們提供的 .proto 檔案 來生成客戶端程式碼,然後就可以串接了。」
  • A. gRPC 不支援跨語言通訊,第三方開發者可能用不同的程式語言
  • B. gRPC 沒有身份驗證機制,公開 API 會有安全性問題
  • C. gRPC 不適合對外公開 API:瀏覽器原生不支援、二進位資料不好除錯、第三方需額外安裝工具鏈增加串接門檻,應該用 REST
  • D. gRPC 使用 HTTP/2 但大部分第三方伺服器只支援 HTTP/1.1,會導致連線失敗

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *