API 設計練習題 (Practice - API Design)
Related Concepts
- 03-API-Design/01-API-Design-Framework
- 03-API-Design/02-REST
- 03-API-Design/03-GraphQL
- 03-API-Design/04-RPC-and-gRPC
- 03-API-Design/05-API-Security
| 場景 | 選擇 |
|---|---|
| Internal 服務通訊 | RPC / gRPC |
| External + over/under-fetching | GraphQL |
| External + no fetching concern | REST(預設) |
| 公開 API 身份驗證 | JWT(Bearer) |
| 第三方授權(Google / GitHub) | OAuth 2.0 |
| 內部微服務雙向驗證 | mTLS |
| 公司內部管理系統 AuthZ | RBAC |
| 金融 / 政府 AuthZ | ABAC |
Part 1:API 設計決策框架(1-05)
Q1(Recall):1-05 PDF 的決策樹有哪兩個提問?分別導向哪些選項?
- External client 還是 Internal? → Internal 直接選 RPC
- Over- 或 Under-fetching 是 concern 嗎?(僅 External 才問)
- Yes → GraphQL
- No → REST
Q2(Recall):REST、GraphQL、RPC 各自的「設計導向」是什麼?
- REST:資源導向(Resource-oriented)— 以 URI 表達資源,用 HTTP 動詞操作
- GraphQL:查詢導向(Query-oriented)— 客戶端決定需要什麼資料
- RPC:方法導向(Method-oriented)— 直接呼叫遠端函式
Q3(Application):你在設計一個內部訂單服務給其他微服務呼叫,應該選什麼?為什麼?
選 gRPC(RPC)。理由:
- 對內通訊 → Internal 分支直接走 RPC
- Protobuf 二進位序列化 → 吞吐量約 JSON 的 10 倍
- IDL 保證跨語言強型別契約
- 支援 streaming(如未來要推訂單狀態更新)
Q4(Application):產品經理說「行動 App 在弱網路下載入很慢,因為 API 回傳一堆用不到的欄位」。你會推薦什麼?
推薦 GraphQL。
- Over-fetching 明確是痛點 → 決策樹走 Yes 分支
- Client-driven query 讓 App 只抓需要欄位 → 省流量、加速弱網路
- 配 persisted queries 還能維持 CDN cache 能力
Part 2:REST 架構風格(1-06)
Q5(Recall):以下哪些 HTTP 方法是冪等的?(A) GET (B) POST (C) PUT (D) PATCH (E) DELETE
冪等:(A) GET、(C) PUT、(E) DELETE
- (B) POST 非冪等:每次呼叫可能建立不同 ID 的物件
- (D) PATCH 依設計而定:
PATCH {age: 30}冪等;PATCH {age: age+1}非冪等。一般不保證。
注意:冪等談的是「伺服器狀態」,不是回應內容。
Q6(Recall):Path Parameter、Query Parameter、Request Body 各自適合什麼用途?
- Path Parameter:標示唯一資源(如
/users/123) - Query Parameter:篩選、排序、分頁(如
?age=30&sort=desc&page=2) - Request Body:傳送結構化資料,用於 POST / PUT / PATCH
Q7(Recall):REST 的 Over-fetching 和 Under-fetching 分別是什麼?
- Over-fetching:API 回傳的欄位比前端需要的多(只要姓名卻拿到整個 user object)
- Under-fetching:一個頁面需要多個資源,要多次 API call 才能取得所有資料
Q8(Recall):REST 中 401 和 403 狀態碼的差別是?
- 401 Unauthorized:沒登入 / token 無效 → 「你是誰?」(Authentication 失敗)
- 403 Forbidden:已登入但沒權限 → 「你不能做這件事」(Authorization 失敗)
Q9(Application):以下 API 設計哪些是反模式?怎麼改?
(A) POST /updateUser
(B) DELETE /users/123
(C) GET /deleteUser?id=123
(D) PUT /users/123 帶完整 user body
(E) POST /getUserList
反模式:(A) (C) (E)
- (A) URL 放動詞 → 改成
PUT /users/{id} - (C) GET 改變狀態 → 改成
DELETE /users/123 - (E) 用 POST 做查詢 → 改成
GET /users?...
(B) 和 (D) 都符合 RESTful 慣例。
Q10(Analysis):為什麼 GET 不應該帶 Request Body?
- 快取失效:CDN / 瀏覽器 / proxy 用 URL 當 cache key,不看 body
- 規範模糊:HTTP spec 未明確禁止,但許多實作(如某些 proxy、client library)會丟掉 GET body
- 語義衝突:GET 應該是「只靠 URL 就能表達的讀取操作」
正確做法:用 Query Parameter,或把查詢改成 POST /search(並犧牲快取)。
Q11(Analysis):為什麼重試非冪等 API(如 POST /payments)很危險?怎麼解?
危險:網路閃斷時 client 重試,可能導致重複扣款。
解法:冪等鍵(Idempotency Key)
POST /payments
Idempotency-Key: abc-123-xyz
Server 記錄這個 key 處理過的結果 → 同 key 重試只處理一次,第二次直接回前次的 response。
時效通常 24 小時或更短。
Part 3:GraphQL(1-07)
Q12(Recall):GraphQL 的三種操作分別是什麼?各自對應什麼用途?
- Query:讀取資料
- Mutation:修改資料(新增、更新、刪除)
- Subscription:即時資料推送(類似 WebSocket / SSE)
Q13(Recall):GraphQL 的 N+1 問題是什麼?如何解決?
N+1 問題:resolver 是 field-based 設計,巢狀查詢會導致:
- 1 次查詢:
SELECT * FROM users LIMIT 10 - N 次查詢:每個 user 各跑一次
SELECT * FROM orders WHERE user_id = ? - 總計 1 + N 次 DB 查詢
解法:
- DataLoader:batch 同一 tick 內的查詢成
IN (...) - Resolver 層直接 JOIN
- Persisted queries + query-specific 優化
Q14(Recall):為什麼 GraphQL 的 HTTP cache 比 REST 困難?
- REST:一個資源對應固定 URL → CDN / 瀏覽器可直接用 URL 當 cache key
- GraphQL:所有查詢都 POST 到
/graphql,不同 query body 產生不同結果 → URL 無法當 key
解法:
- Persisted queries:用 query hash 當 key,可走 GET,恢復 CDN cache
- Apollo / Relay client cache:normalized cache by entity ID
Q15(Application):一個電商 App 首頁要顯示「使用者資料 + 最近訂單 + 未讀通知」。用 REST 和 GraphQL 分別怎麼做?
REST:
GET /users/me
GET /users/me/orders?limit=5
GET /users/me/notifications?unread=true
3 次 API call → under-fetching 問題。
GraphQL:
query {
me {
name
orders(limit: 5) {
id
total
}
notifications(unread: true) {
id
message
}
}
}
一次查詢解決。
Q16(Analysis):為什麼面試官常建議「不要預設推 GraphQL」?
- 面試題通常需求固定 → GraphQL 的靈活性優勢不明顯
- GraphQL 實作複雜度高(schema、resolver、N+1、cache)
- REST 在簡單 CRUD 或需 HTTP cache 的場景更直接
正確策略:只在問題明確聚焦於「靈活查詢 / over-fetching」時才提 GraphQL;否則預設 REST。
Part 4:RPC 與 gRPC(1-08)
Q17(Recall):gRPC 使用什麼序列化格式?為什麼比 JSON 快?
Protocol Buffers(Protobuf),二進位格式。
比 JSON 快的原因:
- 無欄位名稱(用 tag number)→ 更緊湊
- 可變長度編碼(varint)→ 小數字佔更少 byte
- Schema 事先知道 → 不需解析結構
吞吐量約為 JSON 的 10 倍。
Q18(Recall):gRPC 支援哪四種呼叫模式?各舉一個使用場景。
| 模式 | 場景 |
|---|---|
| Unary | 一般 CRUD API |
| Server streaming | 推送事件、行情 feed |
| Client streaming | 大量資料上傳 |
| Bidirectional streaming | 聊天室、即時協作 |
Q19(Application):在面試中,對外 API 和內部微服務通訊分別應該用什麼協定?
- 對外 API:REST(或 GraphQL),因為簡單、跨平台、易測試、瀏覽器原生支援
- 內部微服務通訊:gRPC,因為效能高、強型別、支援 streaming
口訣:「REST/GraphQL 對外,gRPC 對內」
Q20(Analysis):gRPC 的「耦合度高」具體表現在哪裡?怎麼緩解?
表現:
- Client 和 Server 都依賴相同的
.proto檔 - Schema 改動需同步更新雙方 stub
- 跨語言 build pipeline 複雜
緩解:
- Protobuf 相容規則:新欄位用新 tag、刪除欄位保留
reserved、不改現有欄位型別 - Schema registry:集中管理 proto 檔,版本化發佈
- CI 檢查:breaking change detection(如 buf CLI)
Part 5:API Security(1-09)
Q21(Recall):Authentication 和 Authorization 的差異是什麼?
- Authentication(身份驗證):確認「你是誰」
- Authorization(授權):決定「你能做什麼」
先 AuthN 後 AuthZ。
Q22(Recall):以下配對何者正確?
(A) Authentication = 你能做什麼
(B) Authorization = 你是誰
(C) JWT 通常放在 Authorization: Bearer header
(D) OAuth 2.0 主要用於內部微服務通訊
答案:(C)
- (A) 錯 — Authentication 是「你是誰」
- (B) 錯 — Authorization 是「你能做什麼」
- (C) 對 — JWT 以
Authorization: Bearer <token>攜帶 - (D) 錯 — 內部微服務通常用 mTLS,OAuth 2.0 用於第三方授權
Q23(Recall):RBAC 和 ABAC 的差異是什麼?各舉一個例子。
- RBAC(Role-Based):根據角色控權。
例:Admin 可以 DELETE;Viewer 只能 GET - ABAC(Attribute-Based):根據屬性(時間、地點、部門等)控權。
例:只有辦公室 IP 網段才能呼叫/internal-api
RBAC 簡單但角色會爆炸;ABAC 彈性高但 policy 引擎複雜。
Q24(Application):設計一個公開的「第三方應用存取用戶 Google Drive」的授權流程,應該用什麼機制?
OAuth 2.0 + Scope。
流程:
- User 被重導到 Google Authorization Server
- User 同意授權(如 scope =
drive.readonly) - Google 發 access token 回第三方 App
- 第三方 App 帶 token 呼叫 Google Drive API
- API 根據 scope 決定能做什麼
為什麼不用 API Key / JWT? API Key 粒度太粗;JWT 本身不處理「用戶同意」流程。
Q25(Application):公司有一個內部管理系統,需要控制 Admin / Editor / Viewer 的權限。前端是 SPA,後端是多個微服務。你會怎麼組合 AuthN + AuthZ?
Authentication:
- 前端登入 → 後端發 JWT(含 user_id、roles)
- JWT 放在
Authorization: Bearerheader - Access token 短期(15 分鐘) + Refresh token
微服務間:
- mTLS(如果環境允許,透過 service mesh 自動)
- 或轉傳原始 JWT,下游服務自行驗章
Authorization:
- RBAC:JWT payload 含
roles: ["editor"],後端對每個 endpoint 檢查 - 複雜資源(如
/articles/{id})額外檢查 ownership(ABAC 成分)
回應:401(沒登入 / token 過期)vs 403(有身份但無權限)。
Q26(Analysis):JWT 有哪些常見安全陷阱?如何避免?
| 陷阱 | 防範 |
|---|---|
| Payload 只是 base64,不是加密 | 不放敏感資料(如密碼、隱私個資) |
alg: none 漏洞 |
伺服器固定允許的 algorithm,不接受 client 指定 |
| 無法主動撤銷 | Access token 設短期(幾分鐘)+ refresh token 機制 |
| 簽章密鑰外洩 | 使用非對稱演算法(RS256);定期輪替 |
| Token 被竊取(XSS、HTTPS 中間人) | HttpOnly cookie 或 Secure storage;強制 HTTPS |
| Refresh token 被盜 | Refresh token rotation(用過就換新的) |
| 問題 | 解法 |
|---|---|
| 對內 vs 對外 API | 對內 gRPC;對外 REST / GraphQL |
| Over/Under-fetching | GraphQL + DataLoader + persisted queries |
| 非冪等 API 重試 | Idempotency-Key header |
| REST 快取 | URL + HTTP cache headers(ETag / Last-Modified) |
| GraphQL 快取 | Persisted queries / Apollo-Relay client cache |
| 公開身份驗證 | JWT(Bearer)+ Refresh token |
| 第三方授權 | OAuth 2.0 + Scope |
| 內部微服務 | mTLS |
| 內部權限控制 | RBAC |
| 金融 / 政府細粒度權限 | ABAC |
| 401 vs 403 | 401 未認證 / 403 無權限 |