測驗:API 通訊模式 — REST、GraphQL、gRPC、WebSockets
共 5 題,點選答案後會立即顯示結果
1. REST API 的「無狀態(Stateless)」原則是什麼意思?
2. GraphQL 主要解決了 REST 的哪兩個問題?
3. gRPC 比 REST 效能更好的關鍵原因是什麼?
4. 為什麼聊天室適合使用 WebSockets 而不是 HTTP 輪詢(Polling)?
5. 你正在設計一個系統,包含:對外的商品查詢 API、內部微服務間的高頻通訊、以及即時客服聊天功能。根據文章的決策流程,最合適的組合是?
本篇是「系統設計 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 WebSocket或ws.onmessage— 這是 WebSocket,確認有處理斷線重連的情況 - [ ] 選擇通訊模式時 — 先問自己:需要即時嗎?是內部通訊嗎?需要彈性查詢嗎?
本篇重點回顧
- REST 是最通用的選擇,基於 HTTP 的資源導向設計,適合大部分 CRUD 場景
- GraphQL 解決了 REST 的 over-fetching/under-fetching 問題,讓客戶端精確指定要什麼資料
- gRPC 使用 Protocol Buffers 傳輸二進位資料,適合微服務間的高效能通訊
- WebSockets 建立持續的雙向連線,適合即時通訊場景
- 選擇通訊模式的核心問題:即時?內部?彈性查詢?都不是就用 REST
下一篇我們將探討「資料庫」– 系統設計中最重要的資料儲存層。
進階測驗:API 通訊模式 — REST、GraphQL、gRPC、WebSockets
共 5 題,包含情境題與錯誤診斷題。