# ADR-0002：用 desired / reported state 而不是命令式 RPC

已採用

## 狀態

已採用

## 背景

Server 需要讓裝置播放特定的內容。最直覺的做法是下命令：「下載這個檔案」、「切換到那個版面」、「重新啟動播放器」。

## 決策

Server 不下命令，而是宣告一個目標狀態：

```json
{ "version": 42, "defaultLayout": { … }, "schedules": [ … ], "assets": [ … ] }
```

裝置回報現況，並自己負責把差距補上。少數真正屬於「動作」的操作（強制同步、重新啟動播放器）才用命令表達。

## 理由

**命令式的模型必須記住每一條命令的狀態。** 裝置在下載到一半時斷電，Server 怎麼知道？它得記錄命令送出、命令確認、命令失敗、命令重送——這就是在資料庫裡重新發明一套不可靠的工作流程引擎，而且每加一種操作就要多一套狀態。

**宣告式的模型沒有中間狀態要記。** 裝置重新開機後只做一件事：比較目標與現況，把差距補上。中間斷過幾次、重試過幾輪、命令有沒有送到，都不影響最終結果。這是冪等的。

**除錯也容易得多。** 「這台裝置為什麼播錯東西」在命令式模型下要去翻命令歷史，在宣告式模型下只要比對兩個 JSON。

這個模式在基礎設施領域已經被驗證很久了——Kubernetes 的控制器、Terraform 的計畫執行、各種設定管理工具，用的都是同一個想法。

## 後果

- 裝置端的邏輯比較複雜：它必須自己算出差異、排下載順序、處理部分失敗。這個複雜度集中在 `@huan/device-core` 的調和器裡，而且是可以單獨測試的。
- Server 端變得很單純：算出目標、寫進資料庫、發一個通知。
- 版本號必須只在內容真的改變時才遞增。否則改一個不相干的排程就會讓所有裝置誤以為自己落後，然後同時來要狀態。

## 替代方案

**純命令式 RPC**：如上所述，需要在 Server 端維護一套可靠的命令狀態機。

**混合模型**：內容用宣告式、動作用命令式。這其實就是最終採用的做法——`force_sync`、`restart_player` 與 `unbind` 是命令，因為它們本來就是動作而不是狀態。
