iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Software Development

文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念系列 第 7

Day 7:單一真實來源(SSOT)——原始狀態(Raw State)與衍生狀態(Derived State)的邊界

  • 分享至 

  • xImage
  •  

昨天我們把訂單資料正規化成 byId 物件加 allIds 陣列,解決了「查找快不快」的問題。但正規化只處理了資料怎麼擺放,沒有處理另一個更容易出錯的問題:像「代購總金額」這種算出來的數字,該存在哪裡?如果隨手把它存成一個獨立欄位,遲早會遇到「單價改了,總金額卻沒跟著變」這種 Bug。今天要談的單一真實來源(SSOT),就是專門解決這個問題的原則。

一、原始狀態(Raw State)

定義:系統中最核心、不可拆解的基礎資料,通常直接來自 API 響應或使用者的即時輸入。

核心特性:這是系統的「單一真實來源」。原始狀態應該保持最簡化、正規化(Normalized),不包含任何計算後的結果。

二、衍生狀態(Derived State)

定義:由原始狀態經過運算、過濾、排序或格式化後產生的資料。

核心特性:衍生狀態不應被單獨儲存。為了確保即時性與一致性,衍生狀態通常在渲染層或狀態管理器(如 Selector、Computed Property)中即時計算產生。

三、邊界與同步原則

若把衍生狀態當成真實來源存起來,原始狀態一變,就得手動去更新所有跟它相關的衍生狀態,很容易漏掉某一處忘記更新,畫面上出現對不起來的數字。SSOT 的做法是反過來:只存原始狀態,衍生狀態每次都重新計算,永遠不會有「忘記同步」這件事,因為它根本沒有被單獨存過。

沒有 SSOT 會怎麼壞

// 反例:把算出來的總金額也存成獨立欄位
const order = {
  itemPrice: 1200,
  exchangeRate: 32.5,
  shippingFee: 300,
  total: 39300  // 這個數字是當初算好存進去的
};

// 後來買家改了商品單價
order.itemPrice = 1500;
// order.total 還是 39300 —— 沒有人記得要重算它
// 畫面顯示的總金額,從這一刻起就是錯的

正確做法

// 只存原始狀態
const order = {
  itemPrice: 1500,
  exchangeRate: 32.5,
  shippingFee: 300,
  serviceFeeRate: 0.1
};

// 衍生狀態:每次都重新算,不存
function getTotal(order) {
  const subtotal = order.itemPrice * order.exchangeRate;
  const serviceFee = subtotal * order.serviceFeeRate;
  return subtotal + order.shippingFee + serviceFee;
}

order.itemPrice 改了之後,下一次呼叫 getTotal(order) 自動就是對的,不需要記得去同步任何東西——因為 total 從來就沒有被單獨存在別的地方。

四、代購 App 案例:訂單金額計算

在代購 App 裡,區分原始狀態與衍生狀態,是精確計算費用的關鍵:

  1. 原始狀態(Raw State):後端 API 傳回的訂單細項——商品單價、匯率、國際運費、代購服務費比例。這是資料庫裡的單一真實來源。
  2. 衍生狀態(Derived State):由前端即時計算的數值,如商品小計、稅額、代購總金額(商品+運費+服務費)。
  3. 實作架構:前端狀態管理工具只儲存原始狀態。當買家調整訂單數量或服務費比例時,這些原始數據更新,衍生狀態自動重新計算並渲染於畫面上。這種架構保證了不管 UI 邏輯多複雜,買家看到的總金額永遠跟原始資料精準同步。

五、結論

SSOT 不只是架構原則,更是防禦性編程的核心。把原始狀態跟衍生狀態的邊界劃清楚,就能消除「同一個數字存在兩個地方,卻對不起來」這種維護成本。養成「只存原始基礎,即時算衍生結果」的習慣,代購這種牽涉複雜金額計算的系統,才不會三不五時冒出算錯錢的 Bug。

我卡在哪裡:一開始一直在想,為什麼單價改了,總金額不會自動跟著變,這不就是拉一個 Excel 表格套公式那麼簡單的事嗎?後來想到「萬一這個公式本身要改呢」才想通——如果公式散落在很多地方各自算一次,公式一改,就得記得所有地方都跟著改,這其實跟「忘記同步」是同一個問題,只是換了個地方發生。SSOT 把原始狀態跟衍生狀態的界線劃清楚,等於也把「公式」收斂到單一個地方定義,兩者各司其職,不管資料怎麼變、公式怎麼改,算出來的結果才會一直是對的。


上一篇
記憶體中的資料組織——Array vs. Object 在資料查詢與更新上的工程權衡
下一篇
Day 8:單向資料流(Unidirectional Data Flow)——為什麼雙向綁定容易引發狀態失控?
系列文
文科生的軟體工程啟蒙:用一個代購 App,看懂 30 個系統設計觀念8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言