設計聊天行動 App (Mobile Chat)

Medium★★★★Draft

FromMobile System Design Interview

Concepts practiced🔌 即時更新:Long-Polling / WebSocket / SSE🔌 同步 vs 非同步通訊 (Sync vs Async)

Convention: read the requirements & constraints first, sketch your own design, then expand the reference to compare.

📚 From the Books

設計聊天行動 App (Mobile Chat)ProblemMediumMobile

設計一對一聊天 App 的行動客戶端架構與它跟後端的通訊。難點不在後端中繼,而在 app 端:即時連線、本地訊息 DB、離線送出佇列、訊息狀態與可靠排序。

📋 Requirements

🧠 Core Ideas

TIP

核心是「混合通訊(HTTP 送出 + WebSocket 收即時)+ 本地關聯式 DB 當真實來源 + 離線送出佇列與重試 + 訊息狀態機驅動 UI」。

客戶端彼此不直接通訊,透過後端中繼。兩類互動要分開處理(見同步 vs 非同步):客戶端主動操作(送訊息、同步)用 HTTP request-response;後端主動推送(新訊息、已讀、狀態)用即時更新機制。App 啟動時建立 WebSocket 連線並持續保持。

⚖️ Reference Design

通訊協定選型 — 為何是 HTTP + WebSocket
  • 伺服器主動連線可選 Polling/Long polling/SSE/WebSocket。Polling 在無新資料時浪費;Long polling 逾時與多伺服器路由複雜;SSE 單向、仍需另一機制處理上行。
  • 混合架構:HTTP + REST 處理客戶端主動操作;WebSocket 接收即時推送。
  • 選 WebSocket 而非 SSE:延遲更低、header overhead 小,對頻繁小訊息(輸入指示、已讀回條)關鍵;建於 TCP 之上提供可靠、依序傳輸。
  • 嚴格限縮 WebSocket 只用於即時功能,以控管持久連線的擴展複雜度。
客戶端分層架構
  • 採分層架構,遵循 UDF、reactive programming、關注點分離。
  • 資料層核心是 Messages Repository(管理所有讀寫),搭配 Users Repository。
  • 雙資料來源:Messages Local DataSource 走 HTTP 端點;Real-time Updates Remote DataSource 維持 WebSocket 接收即時更新。
  • UI 層兩個主畫面 Chat List 與 Conversation;Message List 依時序顯示所有訊息,含失敗/待送出訊息並提供重送。
本地儲存 — SQLite
  • key-value 儲存不適合成長中的訊息歷史與複雜關係,選關聯式資料庫(訊息本質結構化,需複雜查詢、ACID、可擴充 schema)。
  • 行動端用 SQLite(iOS:CoreData 可以 SQLite 為底、或 SQLite.swift/FMDB;Android:Jetpack Room/SQLDelight)。
  • Messages(已同步)與 MessageRequests(未同步、尚無 message id)分表,Users、Messages、MessageRequests 全放同一 chats.db;為常查欄位加索引、批次交易、所有 DB 操作在背景執行緒避免阻塞 UI。
訊息排序 — 混合策略
  • 邊界案例:網路延遲導致亂序、離線訊息重連後整合、裝置時鐘不一致。
  • 候選排序欄位:messageId(伺服器產生、各端一致,但離線時尚無)vs createdAt(低延遲、支援離線,但受時鐘差影響)。
  • 混合策略:已被後端確認的訊息依 messageId 排序;等待/送出中的訊息依 createdAt 排序並永遠置於列表末端。
  • messageId 由後端產生、必須可依時間排序(新訊息 ID 恆大於舊),後端是 ID 的真實來源。
送出訊息 — 狀態機與離線重試
  • 訊息生命週期:Repository 立即建立並存本地、UI 馬上顯示(樂觀更新)→ 封裝 NewMessageRequest 經 HTTP 送後端 → 後端產生 messageId 並回應 → 客戶端收回應後以 messageId 更新本地。
  • requestIdidempotency key,讓後端對重複請求去重;fromMessageRequestId 把回應對應回原請求。
  • MessageRequest(狀態 DRAFT/PENDING/SENT/FAILED)追蹤送出中訊息;送達確認後移除 MessageRequest、建立新 Message(刻意無 SUCCEEDED 狀態,UI 顯示時合併兩表)。
  • 失敗/逾時:標記 FAILED、failCount++,以指數退避重試;達最大重試次數後標永久失敗並讓使用者手動重送或刪除。
推播通知
  • 接收者無活躍 WebSocket 連線時,後端向 Push Provider(Android FCM/iOS APNs)發送;後端維護使用者↔裝置 token 映射。
  • App 在前景時由 Push Notifications Client 處理並觸發同步,背景/終止時顯示於通知列。
  • 挑戰:系統限縮/省電延遲、需使用者同意、雙平台 API 不一致。因此推播只當輕量警示,即時功能仍靠 WebSocket。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading