API Design
Interview Context
DS/ML 工程師遲早要把 model 包成 API 給下游用,也要天天呼叫別人的 API 抓資料。面試官想確認你能設計一個乾淨、可擴展、不會被 retry 打爆的 endpoint,也想知道你被第三方 API rate limit 時知道怎麼優雅地處理 — 這是「能不能獨立把東西上線」的重要信號。
What You Should Understand
- 能解釋 REST 的核心概念:resources、HTTP verbs、statelessness、URL 設計慣例
- 知道 REST、GraphQL、gRPC 各自的運作方式和適用場景
- 能描述 API gateway 做的每一件事,以及它和 load balancer、reverse proxy 的分工
- 知道常見的 API versioning 策略和各自的 tradeoffs
- 能列出 API 效能優化的主要手段(pagination、caching、async、compression)並系統性地 debug 慢 API
- 理解 idempotency key、exponential backoff、circuit breaker 如何讓 API 呼叫變可靠
REST Fundamentals
REST (Representational State Transfer) is an architectural style, not a protocol. Core ideas:
| Principle | Meaning | 白話說明 |
|---|---|---|
| Resources | Everything is a resource identified by a URL | 每個「東西」(user、order、model)都有自己的網址 |
| HTTP verbs | Actions are expressed by standard methods | 動作用 GET/POST/PUT/DELETE 表達,不寫進 URL |
| Statelessness | Each request contains everything the server needs | Server 不記 session — 每個 request 自帶身分與 context |
| Uniform interface | Same conventions everywhere | 學會一個 endpoint 的用法,其他 endpoint 也猜得到 |
Statelessness 是 scalability 的關鍵:因為任何 server 都能處理任何 request,load balancer 可以自由分配流量、server 可以隨時加減。
HTTP Verbs
| Verb | Semantics | Idempotent? | Safe? | Typical Use |
|---|---|---|---|---|
| GET | Read a resource | Yes | Yes | Fetch predictions, list users |
| POST | Create / trigger an action | No | No | Create a job, submit a batch predict request |
| PUT | Replace a resource entirely | Yes | No | Overwrite a config object |
| PATCH | Partially update a resource | Not guaranteed | No | Update one field of a model registry entry |
| DELETE | Remove a resource | Yes | No | Delete an experiment |
Idempotent = 同一個 request 重送多次,結果和送一次一樣。Safe = 不改變 server state。這兩個屬性決定了「哪些 request 可以放心 retry」— GET/PUT/DELETE 可以直接 retry,POST 需要 idempotency key(後面會講)。
Anatomy of a URL
https://api.example.com/v1/users/123/orders?status=shipped&sort=-created_at&page=2
\___/ \_____________/ \_/ \___________________/ \______________________________/
scheme host version resource path query parameters
| Part | Example | Role |
|---|---|---|
| Scheme | https | Protocol — production API 一律 HTTPS |
| Host | api.example.com | 通常用獨立 subdomain 和主站分開 |
| Version | v1 | API version(見 Versioning 一節) |
| Resource path | /users/123/orders | Hierarchy: collection → item → sub-collection |
| Query params | ?status=shipped | Filtering, sorting, pagination — 不改變資源本身 |
REST Design Best Practices
| Practice | Do | Don't |
|---|---|---|
| Nouns, not verbs | POST /predictions | POST /createPrediction |
| Plural resources | GET /models | GET /model |
| Nest for relationships | GET /users/123/orders | GET /getOrdersByUser?id=123 |
| Shallow nesting | /orders/456(頂多兩層) | /users/123/orders/456/items/789/reviews |
| Filtering via params | GET /orders?status=shipped | GET /orders/shipped |
| Sorting via params | GET /orders?sort=-created_at | 為每種排序開新 endpoint |
| Pagination via params | GET /orders?limit=20&cursor=abc | 一次回傳全部資料 |
| Standard status codes | 201 Created, 404 Not Found, 429 Too Many Requests | 全部回 200 然後把錯誤塞在 body |
動詞放進 URL 是最常見的 code smell
看到 /getUser、/updateOrder、/doPredict 這種 URL,面試官會直接扣分。HTTP verb 已經表達了動作 — URL 只描述「資源是什麼」。唯一常見的例外是不好對應到 CRUD 的操作(例如 /models/123:promote 這種 action-style endpoint),但要先窮盡 resource 的表達方式再考慮。
REST vs GraphQL vs gRPC
三種主流 API paradigm 的運作方式:
REST: Client 對 resource URL 發 HTTP request,server 回傳(通常是 JSON 的)resource representation。一個 endpoint 對應一種 resource — 需要多種資料就打多個 endpoint。
GraphQL: Client 送一段 query(描述「我要哪些欄位」)到單一 endpoint,server 依 schema 解析後只回傳被要求的欄位。解決 REST 的 over-fetching(拿到不需要的欄位)和 under-fetching(一個畫面要打好幾個 endpoint)問題。
gRPC: 基於 HTTP/2 的 RPC framework。用 Protocol Buffers 定義 service 和 message(強型別 schema),資料以 binary 編碼傳輸 — payload 比 JSON 小很多、序列化快很多,還支援 streaming。缺點是 browser 原生支援差,主要用於 server 之間的內部通訊。
| Dimension | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocol | HTTP/1.1 or HTTP/2 | HTTP (single POST endpoint) | HTTP/2 |
| Payload | JSON (text) | JSON (text) | Protobuf (binary, compact) |
| Typing | 無強制 schema(可用 OpenAPI 補) | Strong schema (SDL) | Strong schema (.proto) |
| Caching | 極好 — HTTP caching 直接可用 | 難 — 都是 POST,需要 client-side cache | 難 — binary + POST-like |
| Streaming | 無原生支援(可用 SSE/WebSocket) | Subscriptions | Native bi-directional streaming |
| Best for | Public API、CRUD、簡單整合 | 前端聚合多資料源、mobile 省流量 | 內部 microservices、低延遲高吞吐 |
Where a DS/ML Team Meets Each
| Paradigm | Typical Encounter |
|---|---|
| REST | 把 model 包成 /predict endpoint、呼叫第三方資料 API(Stripe, OpenWeather)、feature store 的 REST 介面 |
| GraphQL | 公司前端團隊用 GraphQL gateway 聚合資料 — 你的 ML service 是它背後的一個 data source |
| gRPC | TensorFlow Serving 和 Triton 的原生介面、內部 feature fetching(latency 敏感)、大量 embedding 傳輸 |
面試怎麼選?
標準答案框架:對外(public、瀏覽器、第三方)用 REST — 生態系與 caching 最成熟;內部 service-to-service 且 latency/throughput 敏感用 gRPC — binary payload + HTTP/2 multiplexing;前端需要彈性聚合多種資料才考慮 GraphQL — 但要接受 caching 和 rate limiting 變複雜的代價。
API Gateway
API gateway 是所有 client request 進入後端的單一入口。它把橫切關注點(auth、rate limiting、logging)從每個 service 抽出來集中處理。一個 request 進入 gateway 後的典型流程:
- Parameter validation — 解析 HTTP request,檢查必要欄位、格式是否合法
- Allow-list / deny-list check — 封鎖已知惡意 IP 或只放行白名單來源
- Authentication & authorization — 呼叫 identity provider 驗證身分(authn)、確認權限(authz)
- Rate limiting — 依 user/IP/endpoint 檢查是否超過額度,超過就回 429
- Dynamic routing — 依 path 把 request 導到正確的 backend service
- Protocol transformation — 例如對外收 REST/JSON,對內轉成 gRPC 呼叫 microservice
- Circuit breaking — 偵測到某個 downstream service 持續失敗時快速失敗,不讓錯誤擴散
- Error handling & fault isolation — 統一格式化錯誤回應,隔離單一 service 的故障
- Logging, monitoring, caching — 記錄 access log、匯出 metrics、cache 熱門回應
Load Balancer vs API Gateway vs Reverse Proxy
三者常被混用,但職責不同:
| Component | Layer | Primary Job | Knows About |
|---|---|---|---|
| Load Balancer | L4 or L7 | 把流量平均分配到多台相同的 server | Server health、分配演算法(round robin, least connections) |
| Reverse Proxy | L7 | 代表 server 接收流量:SSL termination、compression、靜態內容 cache、隱藏後端拓撲 | 後端在哪,但不懂 API 語意 |
| API Gateway | L7 (application-aware) | API 管理:auth、rate limiting、routing、protocol 轉換、versioning | API contract、user identity、business rules |
概念上:reverse proxy 是「泛用的中介」,load balancer 是「專注於分流的 reverse proxy」,API gateway 是「懂 API 語意的 reverse proxy 加上一整套管理功能」。
How they work together — 大型系統常見的串接順序:
Client → DNS → Load Balancer (multi-region / multi-instance)
→ API Gateway (auth, rate limit, routing)
→ internal Load Balancer → Service instances
外層 LB 讓 gateway 本身可以水平擴展;gateway 做完 API 層的檢查後,再由內層 LB 把 request 分配到 service 的多個 instance。
API Versioning Strategies
API 一旦有人在用,任何 breaking change(改欄位名、刪 endpoint、改語意)都會弄壞 client。Versioning 讓你能演進 API 而不破壞既有整合。
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | GET /v2/users | 最直觀、好 debug、curl 一看就懂、容易按版本 route | URL 不再「純粹指涉資源」;版本爆炸時 route 混亂 |
| Query parameter | GET /users?version=2 | 不動 path、預設可 fallback 到最新版 | 容易被忽略、cache key 要把 param 算進去、不夠正式 |
| Header | Accept: application/vnd.api+json;version=2 | URL 最乾淨、符合 HTTP 語意(content negotiation) | 不能直接在瀏覽器測、對新手不友善、debug 麻煩 |
實務上 URL path versioning 最流行(Stripe、Twitter、OpenAI 都是 /v1/),因為顯式、好觀察、好 route。原則:只有 breaking change 才需要 bump major version;加欄位、加 endpoint 這種 backward-compatible 改動不用。
Semantic Versioning
Library 和 API contract 常用 SemVer 表達版本號:MAJOR.MINOR.PATCH(例如 2.4.1)。
| Segment | Bump When | Example |
|---|---|---|
| MAJOR | Incompatible / breaking API changes | 移除欄位、改回傳格式 → 2.4.1 becomes 3.0.0 |
| MINOR | Backward-compatible new functionality | 加一個 optional 參數 → 2.4.1 becomes 2.5.0 |
| PATCH | Backward-compatible bug fixes | 修一個計算錯誤 → 2.4.1 becomes 2.4.2 |
Pre-release 標記如 1.0.0-alpha、1.0.0-rc.1 表示尚未穩定。0.x.y 版本代表「API 還沒承諾穩定,隨時可能變」。
API Performance
The Top Optimization Levers
| Lever | How It Works | When to Use |
|---|---|---|
| Pagination | 一次只回一頁(limit + offset 或 cursor) | List endpoint 的資料量會成長時 — 幾乎永遠 |
| Caching | Client cache(Cache-Control, ETag)、CDN、server-side cache(Redis) | 讀多寫少、可以容忍些微 staleness 的資料 |
| Connection pooling | 重用 TCP/DB connection,省去反覆 handshake | 任何高頻對 DB 或 downstream 的呼叫 |
| Async / queue for slow work | 慢工作丟進 queue,立刻回 202 + job id,client 之後 poll 或收 webhook | 任務超過 1-2 秒(batch prediction、report 生成) |
| Payload compression | gzip / brotli 壓縮 response body | 大 JSON payload、頻寬受限的 mobile client |
| N+1 avoidance | 一次 batch query 取代迴圈裡的 N 次 query | List endpoint 要帶出關聯資料時(每筆 order 再查一次 user) |
Pagination: Offset vs Cursor
| Approach | Example | Pros | Cons |
|---|---|---|---|
| Offset-based | ?limit=20&offset=40 | 簡單、可跳頁 | Deep offset 慢(DB 要掃過前面所有 rows);資料插入時會漏或重複 |
| Cursor-based | ?limit=20&cursor=eyJpZCI6MTIzfQ | 穩定、深頁也快(用 index seek) | 不能跳頁、cursor 要編碼排序鍵 |
推薦系統或 feed 類 API 一律用 cursor-based — 因為資料一直在插入,offset 分頁會讓 user 看到重複內容。
How to Debug a Slow API
面試被問「你的 API 很慢,怎麼查?」— 沿著 request 的生命週期逐段量測,不要亂猜:
| Stage | What Goes Wrong | How to Check / Fix |
|---|---|---|
| DNS resolution | 慢的 DNS provider、無 cache | curl -w timing、換 DNS、加 TTL cache |
| TCP connection | 每個 request 都重新 handshake | Connection pooling、keep-alive |
| TLS handshake | 反覆 full handshake | TLS session resumption、HTTP/2 重用連線 |
| Server processing | 最常見:慢 query(missing index)、N+1、blocking synchronous calls、CPU-heavy 邏輯 | Profiling 找 hot path、加 index、batch query、慢工作丟 async |
| External API calls | 第三方服務慢,拖垮整體 | 平行呼叫、aggressive timeout、cache 第三方回應 |
| Serialization | 巨大 JSON 的 encode/decode | 縮小 payload、用 faster serializer、gRPC/protobuf |
| Network transfer | Payload 太大、client 太遠 | Compression、CDN、pagination |
| Infrastructure | Server 滿載、connection pool 上限 | Auto-scaling、調 pool size |
Measure First
面試中最強的信號是先講「量測」再講「解法」:先看 p50/p95/p99 latency 分辨是普遍慢還是尾端慢,再用 tracing(如 OpenTelemetry)拆出每一段的耗時,找到實際 bottleneck 才動手。直接跳「加 cache」的候選人會被追問到死。
Idempotency & Reliability
Network 是不可靠的:request 可能送達但 response 掉了。此時 client 不知道操作有沒有成功 — retry 可能造成重複扣款、重複建 job。可靠的 API 設計要同時處理「怎麼安全地重試」和「失敗時怎麼不雪崩」。
Idempotency Keys
機制:client 為每個操作產生一個唯一的 key(通常是 UUID),放在 header 如 Idempotency-Key 中。Server 第一次看到這個 key 就執行操作並把結果存起來;之後帶同一個 key 的 request 不重新執行,直接回傳存好的結果。
- POST(non-idempotent)因此變得可以安全 retry — Stripe 的付款 API 是經典範例
- Key 通常設定 TTL(例如 24 小時)後過期
- Server 端用 unique constraint 或 Redis SETNX 保證同 key 只執行一次
Retries with Exponential Backoff + Jitter
Retry 的原則:只 retry 可能是暫時性的失敗(timeout、429、500/502/503),不 retry 明確的 client error(400、401、404 — 重送一百次也不會變對)。
Naive 的固定間隔 retry 會讓所有 client 同時重試 — 對已經在掙扎的 server 形成 retry storm。解法是 exponential backoff with jitter:
- Exponential: 等待時間 1s → 2s → 4s → 8s,給 server 恢復的時間
- Cap: 上限(例如 60s),避免等到天荒地老
- Jitter: 乘上隨機數把 client 的重試時間打散,避免 thundering herd(大家同秒重試)
Timeouts
沒有 timeout 的呼叫是掛掉的開始:一個慢的 downstream 會佔住你的 thread/connection,最後把整個 service 拖垮。原則:每一個對外呼叫都要有 timeout,而且上游的 timeout 要大於下游的 timeout 總和(否則上游先放棄,下游做白工)。
Circuit Breaker Pattern
Retry 解決「偶發失敗」,circuit breaker 解決「持續失敗」— 當 downstream 已經倒了,繼續打它只是浪費資源、拖慢自己。Circuit breaker 是一個有三個狀態的 state machine:
| State | Behavior | Transition |
|---|---|---|
| Closed(正常) | Request 照常通過,統計失敗率 | 失敗率超過 threshold → 跳到 Open |
| Open(熔斷) | 直接 fail fast,不打 downstream | 經過 cooldown 時間 → 進入 Half-Open |
| Half-Open(試探) | 放少量 request 過去試水溫 | 成功 → 回 Closed;失敗 → 回 Open |
Reliability 三件套的關係
Timeout 決定「單次呼叫等多久放棄」,retry with backoff 決定「放棄後怎麼再試」,circuit breaker 決定「什麼時候連試都不要試」。三者疊加才是完整的 resilience 策略 — 面試中能把三者的分工講清楚就是 senior 信號。
Rate Limiting in Practice
Rate limiting 保護 API 不被單一 client(惡意或失控的 retry loop)打爆,也用來實作商業方案的配額(free tier 每分鐘 60 次)。
The Client-Facing Contract
被限流時 server 回 429 Too Many Requests,並用 headers 告訴 client 怎麼辦:
| Header | Meaning |
|---|---|
| Retry-After | 幾秒後可以再試 |
| X-RateLimit-Limit | 這個 window 的總額度 |
| X-RateLimit-Remaining | 還剩幾次 |
| X-RateLimit-Reset | 額度何時重置(timestamp) |
良好的 client 收到 429 應該讀 Retry-After 並配合 backoff — 而不是立刻重試(那只會吃掉更多額度)。
Rate Limit Keys: Per-User vs Per-IP
| Key | Pros | Cons | Use When |
|---|---|---|---|
| Per-user / per-API-key | 精準、可依方案分級 | 需要 authentication 才能識別 | 已登入的 API(大多數 SaaS) |
| Per-IP | 不需要身分、擋匿名濫用 | NAT 後多人共用一個 IP 會誤傷;攻擊者換 IP 就繞過 | 公開 endpoint(登入頁、註冊) |
| Per-endpoint | 保護特別貴的操作 | 管理複雜 | 昂貴 endpoint(batch predict、report export) |
實務上常疊加使用:per-IP 擋匿名濫用 + per-key 實作方案配額 + 貴的 endpoint 額外收緊。
Algorithms Recap
| Algorithm | How It Works | Pros | Cons |
|---|---|---|---|
| Fixed window | 每分鐘一個 counter,超過就擋 | 最簡單、省記憶體 | 邊界問題:兩個 window 交界處可瞬間打進 2x 流量 |
| Sliding window log | 記錄每個 request 的 timestamp,數過去 60 秒內的數量 | 完全精準 | 每個 request 都要存 — 記憶體貴 |
| Sliding window counter | 用前後兩個 window 的 counter 加權近似 | 精準度和成本的好平衡 | 是近似值,非精確 |
| Token bucket | Bucket 以固定速率補充 token,每個 request 消耗一個 | 允許短暫 burst、實作簡單 | 參數(bucket size, refill rate)要調 |
| Leaky bucket | Request 進 queue,以固定速率流出處理 | 輸出速率平滑穩定 | Burst 會被排隊延遲,不適合 latency 敏感場景 |
Token bucket 是業界最常見的選擇(AWS、Stripe 都用)— 因為它天然容忍合理的 burst,符合真實流量的形狀。
Real-World Use Cases
Case 1: ML Model Serving API(batch predict 與 version 管理)
你的團隊要把 churn model 開放給公司內其他部門使用。單筆預測用 POST /v1/predictions 同步回傳;但行銷部門每週要對 200 萬用戶跑一次全量預測 — 同步 endpoint 會 timeout。
設計:batch 工作走 async pattern — POST /v1/batch-predictions 收到請求後把 job 丟進 queue,立刻回 202 Accepted 加上 job id;client 之後 GET /v1/batch-predictions/234 查狀態,完成後拿到結果檔案的下載連結。Model 升級用 URL versioning(/v1 vs /v2)處理 breaking change(例如 feature schema 改變),而同一個 API version 內部的 model 迭代則透過 response 裡的 model_version 欄位揭露,方便下游 debug「為什麼分數變了」。
面試 follow-up:
- 如果 batch job 跑到一半掛掉,client 重送 request 會發生什麼?(idempotency key 避免重複跑一次 200 萬筆的預測)
- /v1 和 /v2 的 model 需要同時在線多久?怎麼讓下游遷移?(deprecation policy、sunset header、用 access log 追蹤誰還在打 v1)
- 同步 predict endpoint 的 latency budget 怎麼定?(下游服務的 timeout 減去 network overhead,反推 model 推論時間上限)
Case 2: 推薦系統 API 的 Pagination 與 Caching
你負責的推薦 API 提供 GET /v1/users/123/recommendations 給 App 的首頁 feed。流量尖峰時每秒上萬次請求,而且 user 會不斷往下滑載入更多。
設計:分頁必須用 cursor-based pagination — 推薦列表是動態生成的,offset 分頁會在候選池更新時讓 user 看到重複的項目;cursor 編碼「上次看到哪裡」讓結果穩定。Caching 分層處理:推薦結果對同一個 user 短時間內不需要重算,server-side 用 Redis cache 個 5 分鐘(key 是 user id + context);response 帶 Cache-Control: private, max-age=300 讓 client 也能重用 — private 是因為推薦是個人化的,絕不能進 shared CDN cache。熱門的 fallback 推薦(給冷啟動用戶的 popular items)則是全站相同,可以放 CDN。
面試 follow-up:
- Cache 5 分鐘會不會讓「剛買過的商品還被推薦」?(event-driven cache invalidation:purchase event 觸發刪 cache)
- 個人化內容為什麼不能進 CDN?(shared cache 會把 A 的推薦回給 B — privacy 事故)
- 怎麼衡量 cache 的效益?(cache hit rate、p99 latency 前後對比、Redis 與重算的成本比較)
Case 3: 第三方資料 API 抓取的 Retry 與 Rate Limit 應對
你要每天從第三方 API 抓取 50 萬筆商品資料餵給 feature pipeline。對方的限制是每分鐘 600 次請求,而且偶爾會回 500 或 timeout。
設計:Client 端自己實作 token bucket throttling(每秒最多 10 次)主動遵守額度,而不是打到 429 才被動處理 — 被動觸發限流可能導致 API key 被封。收到 429 時讀 Retry-After header 並暫停;收到 500/timeout 時用 exponential backoff + jitter 重試最多 3 次;為整個抓取任務加上 circuit breaker — 如果對方持續回錯,暫停 10 分鐘再試,避免無謂消耗額度。所有 request 記錄 request id 和 response status,方便對帳「今天少了哪些商品」。抓取進度要 checkpoint(記錄抓到第幾個 cursor),任務中斷後從斷點續跑而不是從頭來過。
面試 follow-up:
- 為什麼 backoff 需要 jitter?(多個 worker 同時失敗會同時重試 — thundering herd 讓對方更痛,也讓你的重試更容易再失敗)
- 哪些 status code 不該 retry?(400/401/403/404 — deterministic failure,重試無意義且浪費額度)
- 如果 pipeline 的時間窗不夠抓完 50 萬筆怎麼辦?(提高並行度但總速率仍受 rate limit 約束 → 和對方談 bulk export/webhook、增量抓取只拿 changed records)
Hands-on: API Design Patterns in Python
FastAPI Endpoint with Pagination and Idempotency Key
from fastapi import FastAPI, Header, HTTPException, Query
app = FastAPI()
idempotency_store = {} # in production: Redis with TTL
@app.get("/v1/models/{model_id}/predictions")
def list_predictions(
model_id: str,
limit: int = Query(20, le=100), # cap page size
cursor: str | None = None, # cursor-based pagination
):
rows, next_cursor = fetch_page(model_id, limit, cursor)
return {
"data": rows,
"next_cursor": next_cursor, # null when no more pages
}
@app.post("/v1/batch-predictions", status_code=202)
def create_batch_job(
payload: dict,
idempotency_key: str = Header(...), # client-generated UUID
):
# Same key seen before -> return the stored result, do NOT re-run
if idempotency_key in idempotency_store:
return idempotency_store[idempotency_key]
job_id = enqueue_batch_job(payload) # slow work goes to a queue
response = {"job_id": job_id, "status": "queued"}
idempotency_store[idempotency_key] = response
return response # 202 Accepted + job id; client polls for status
Retry Decorator with Exponential Backoff and Jitter
import random
import time
from functools import wraps
RETRYABLE = {429, 500, 502, 503, 504} # transient failures only
def retry_with_backoff(max_attempts=4, base=1.0, cap=30.0):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return fn(*args, **kwargs)
except TransientAPIError as e:
if e.status not in RETRYABLE or attempt == max_attempts - 1:
raise # non-retryable or out of attempts
# full jitter: random delay in [0, min(cap, base * 2^attempt)]
delay = random.uniform(0, min(cap, base * 2 ** attempt))
time.sleep(delay)
return wrapper
return decorator
@retry_with_backoff(max_attempts=4)
def fetch_products(cursor):
# honor Retry-After on 429 before raising, inside the client
return third_party_client.get("/products", cursor=cursor)
Simple Circuit Breaker
import time
class CircuitBreaker:
"""Closed -> Open on repeated failures; Half-Open after cooldown."""
def __init__(self, failure_threshold=5, cooldown=60.0):
self.failure_threshold = failure_threshold
self.cooldown = cooldown
self.failures = 0
self.state = "closed"
self.opened_at = 0.0
def call(self, fn, *args, **kwargs):
if self.state == "open":
if time.time() - self.opened_at < self.cooldown:
raise CircuitOpenError() # fail fast, skip downstream
self.state = "half_open" # cooldown over: probe
try:
result = fn(*args, **kwargs)
except Exception:
self.failures += 1
if self.state == "half_open" or self.failures >= self.failure_threshold:
self.state = "open" # trip the breaker
self.opened_at = time.time()
raise
self.failures = 0 # success resets the breaker
self.state = "closed"
return result
breaker = CircuitBreaker(failure_threshold=5, cooldown=60)
# result = breaker.call(fetch_products, cursor)
Interview Signals
What interviewers listen for:
- 你的 URL 設計符合慣例(nouns、plural、filtering 用 query params),不會發明 /getUser 這種 endpoint
- 你能依場景選 REST/GraphQL/gRPC 並講清楚 tradeoffs,而不是背「gRPC 比較快」
- 被問到 slow API 時你先講量測(p95/p99、tracing 拆段),再講解法,且知道 N+1 和 missing index 是最常見兇手
- 你會主動提到 idempotency key、timeout、backoff with jitter、circuit breaker — 代表你被 production 教育過
- 你把 rate limiting 當雙向問題:自己的 API 要限流保護,呼叫別人的 API 要主動 throttle 與優雅退避
Practice
Flashcards
Flashcards (1/10)
REST 的 statelessness 是什麼?為什麼它對 scalability 重要?
每個 request 自帶 server 處理所需的全部資訊(身分、context),server 不保存 session state。好處:任何 server 都能處理任何 request,load balancer 可以自由分流、server 可以隨意水平擴展或替換。
Quiz
下列哪個 endpoint 設計最符合 REST 慣例?