# ADR-0001：WebSocket 只送通知，REST 才是事實來源

已採用

## 狀態

已採用

## 背景

裝置需要知道內容什麼時候變了。可行的做法有幾種：

1. 每隔幾秒完整拉一次狀態。
2. 用 WebSocket 把完整的狀態推給裝置。
3. 用 WebSocket 推「有東西變了」，裝置再自己去拉。

## 決策

採用第三種。WebSocket 只傳這種訊息：

```json
{ "type": "desired_state_changed", "version": 42 }
```

裝置收到後打 `GET /api/v1/device/state` 取得完整內容。**REST API 是唯一的事實來源。**

同時保留每 5 分鐘一次的保底同步，不完全依賴 WebSocket。

## 理由

**輪詢太浪費。** 一百台裝置每 10 秒拉一次，就是每秒十次全量查詢，而其中九成九的回應完全相同。

**推完整狀態會有一致性問題。** 推播是「射後不理」的：訊息在傳輸中遺失、裝置剛好在重連、或者兩次推播的順序顛倒，裝置就會停在一個錯誤的狀態，而且沒有人知道。要修這個問題就得加上確認、序號與重送——等於在 WebSocket 之上重新實作一次 TCP。

**通知加拉取沒有這個問題。** 通知遺失最壞的結果是延遲，因為保底同步一定會補上。順序顛倒也沒關係，因為裝置拉到的永遠是當下最新的完整狀態。裝置重連後只要拉一次就同步了，不需要重播任何訊息。

保底同步不是備援設計上的懶惰，而是承認長連線本來就會斷、會被中間設備靜默丟棄、會遇到 NAT 逾時。

## 後果

- 一次變更會產生一次推播加一次 REST 請求，比純推播多一趟往返。對於「幾分鐘才變一次」的數位看板，這完全不是問題。
- WebSocket 完全掛掉時，系統會退化成 5 分鐘輪詢一次，功能不受影響，只是變慢。
- 訊息很小，因此 WebSocket 連線本身的成本很低。

## 替代方案

**Server-Sent Events**：單向、比 WebSocket 簡單。但裝置的 heartbeat 需要往上傳，只用 SSE 就得再開一條 HTTP 通道。WebSocket 一條連線兩個方向，反而更單純。

**長輪詢**：可行，但每次逾時都要重建連線，在幾百台裝置的規模下會產生大量無謂的連線。
