在軟體開發中,我們經常面臨一個棘手問題:當資料格式(Schema)隨著版本更新而改變時,如何確保舊版本的程式能讀新資料,而新版本的程式也能讀舊資料?這就是所謂的相容性問題。如果處理不好,就必須強迫所有使用者一次性升級,或者面對毀滅性的資料損毀。
最近在 Local-first 軟體會議中,Seph Gentle 提出了一套關於持久化資料格式(Durable Data Format)的觀點,並透過一個名為 Schemaboi 的 Rust 實驗性實作,探討如何讓資料在長期演進中依然保持可用性。
為什麼我們需要更強大的資料格式
目前的開發環境通常在兩個極端之間掙扎。一種是傳統的本地軟體,資料鎖在單一裝置中;另一種是雲端軟體,資料被集中在服務商的資料庫裡,使用者實際上並不擁有資料。
為了實現真正的本地優先(Local-first)生態,不同應用程式應該能共享同一個資料層,且彼此獨立演進。這就要求資料格式必須具備三個關鍵特性:自描述性(Self-describing)、強型別(Statically Typed)以及極高的相容性。
對比常見格式的侷限性
在討論解決方案前,我們先看看目前主流格式的痛點。JSON 雖然通用,但它太過冗贅且缺乏強型別定義,不適合需要長期保存且對效能有要求的持久化場景。而 Protocol Buffers 或 Cap’n Proto 雖然高效,但它們依賴預先共享的 Schema 檔案。如果讀取端沒有對應的定義檔,就無法解碼資料。
Schemaboi 採取的策略是將 Schema 直接嵌入到檔案的標頭(File Header)中。這樣任何工具在開啟檔案時,都能立即知道資料的結構,而不需要依賴外部定義檔,實現了真正的自描述。
重新定義三種相容性
在工程實務中,我們通常只討論兩種相容性,但 Gentle 提出了第三種維度:
向後相容性(Backward Compatibility) 這是最常見的情況:新版本的程式能夠正確讀取舊版本產生的檔案。
向前相容性(Forward Compatibility) 這較為困難:舊版本的程式在遇到新版本產生的檔案時,不會崩潰,且能讀取其中它能理解的部分。
向側相容性(Sideways Compatibility) 這是針對分佈式開發的設計。當多個不同的開發者在沒有中央協調的情況下,各自為同一套資料結構增加新欄位時,彼此的變更不會產生衝突。為了達成這一點,Schemaboi 建議使用全球唯一識別碼(Globally Unique Identifiers)來標記欄位,而不是使用簡單的遞增數字標籤(Tags),以避免不同開發者定義了相同索引的欄位。
確保資料完整性的演進策略
為了在版本更迭中保護資料,Schemaboi 採取了兩項核心機制:
預設值填充 當新版程式讀取舊版檔案而發現缺少某些新欄位時,系統會自動使用預設值填充,確保程式邏輯不會因為空值而中斷。
未知欄位保留 這是向前相容性的關鍵。當舊版程式讀取新版檔案時,它會遇到不認識的欄位。此時程式不會直接將這些資料丟棄,而是將其保留。這樣當使用者將檔案存回時,這些新欄位依然存在,不會因為一次舊版軟體的讀寫而導致新功能資料遺失。
Schema 演進的安全指南
並非所有的變更都是安全的。在設計演進路徑時,可以將其分為低風險與高風險操作:
安全操作 增加可選欄位(Optional Fields)或新增列舉值(Enum Variants)。這類變更通常不會破壞現有邏輯。
高風險操作 將可選欄位變更為必填(Mandatory)、重新命名欄位或更改欄位型別。這些操作極易導致舊版程式崩潰或資料解析錯誤。
Gentle 建議工程師應傾向於簡單的增量更新,而非強迫進行大規模的資料遷移。這種思維與 HTTP 或 Email 等長壽協議的成功經驗一致:透過不斷地微小擴充而非推翻重來,才能在長週期中生存。
總結與實務影響
雖然 Schemaboi 目前仍處於實驗階段,但它提供了一個重要的視角:資料應該由使用者擁有,而不是由特定的應用程式定義。
對於開發者而言,設計資料格式時不應只考慮目前的開發速度,更應思考五年後、十年後,當軟體版本迭代數百次後,目前的資料是否依然能被正確解析。透過將 Schema 內建化、使用唯一識別碼以及實施未知欄位保留機制,我們可以構建出真正具有生命力的演進式資料系統。
來源:infoq.com (Evolutionary Data Through Schemaboi: Achieving Forward, Backwards, and Sideways Compatibility)
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。