在開發 Android 應用程式時,我們經常面臨網路不穩定、使用者期待即時更新以及離線操作等挑戰。許多工程師會選擇使用 MVP 或 Clean Architecture 作為起點,但在面對現代行動裝置的「響應式(Reactive)」需求(例如搭配 Jetpack Compose)時,這些傳統模式往往會帶來過多的樣板程式碼(Boilerplate),或無法有效處理複雜的本地與遠端同步問題。
為了克服這些痛點,本文介紹一種專為行動端優化的設計模式:響應式數據層架構(Reactive Data Layer Architecture,簡稱 RDLA)。
為什麼傳統模式在行動端會遇到瓶頸
首先我們來看看 MVP 與 Clean Architecture 在實務上的限制。
MVP 的拉取式瓶頸 MVP 採取的是「拉取(Pull-based)」機制:Presenter 向 Model 請求數據,Model 回傳後再由 Presenter 推送給 View。這種方式在簡單應用中可行,但缺乏自動同步能力。如果後台同步任務更新了資料庫,Presenter 並不會知道,除非它主動再次輪詢或依賴複雜的事件總線(Event Bus)。
Clean Architecture 的樣板稅 Clean Architecture 強調業務邏輯與框架分離,這在後端系統非常完美,但在行動端會產生「樣板稅」。例如,為了遵循層級,開發者必須為每個簡單的讀取操作建立一個 Use Case 類別,而這個類別僅僅是呼叫 Repository 的方法。當應用程式有數十張資料表時,會產生大量毫無業務價值的傳遞類別(Pass-through classes)。此外,它對行動端特有的離線狀態傳播與 SQLite 效能限制缺乏具體指導。
RDLA 的核心理念與設計
RDLA 的目標是將「響應式 UI 框架」與「行動端儲存限制」完美對接。它基於三個核心原則:
響應式推送流:UI 永遠不採取一次性查詢,而是訂閱冷流(Cold Flow),讓數據主動推送。 本地快取為唯一真理(Single Source of Truth):UI 僅從本地資料庫讀取數據,網路請求僅用於填充資料庫。 封裝快取與同步:快取過期檢查、本地編輯合併與背景抓取邏輯全部隱藏在 Repository 實作中。
RDLA 的模組拓撲結構
RDLA 將數據層拆分為三個獨立模組,確保依賴關係單向且清晰:
API 模組(公開) 這是唯一對 UI 層可見的模組。它包含純 Kotlin 的領域模型(Domain Model)與 Repository 介面。這裡不包含任何資料庫或網路框架的依賴,對應 Clean Architecture 的 Entity 層。
實作模組(私有) 所有類別均標記為 internal。它負責協調本地數據源(LocalDataSource)與遠端數據源(RemoteDataSource),處理快取邏輯與數據轉換。
儲存模組(共享儲存) 包含 Room 資料庫與 Retrofit 網路客戶端。這裡處理具體的 SQL 查詢與 API 呼叫,將框架細節完全封裝在最外層。
如何驅動 MVVM 與單向數據流 (UDF)
在 RDLA 中,ViewModel 不再是主動管理數據的同步器,而是一個被動的轉換器(Transformer)。
ViewModel 觀察 Repository 提供的 Flow,並使用 stateIn 運算子將其轉換為 UI 可用的 StateFlow。當後台同步任務更新 Room 資料庫時,資料庫會自動發射新數據,透過 Repository 直接傳遞到 UI。這實現了嚴格的單向數據流(Unidirectional Data Flow),消除了手動刷新數據導致的同步 Bug。
實務操作:離線優先的變更處理(Mutations)
當使用者修改數據時,RDLA 區分兩種處理方式:
同步變更(Synchronous Mutations) 適用於必須在線完成的操作(如刪除醫療紀錄)。若網路失敗,本地資料庫不更新,直接向使用者報錯。
非同步變更(Asynchronous Mutations / Offline-first) 適用於離線優先場景。操作會先寫入本地的「變更隊列(Mutation Queue)」,並即時合併到 UI 的數據流中,讓使用者感覺操作已完成。
為了保證可靠性,RDLA 將背景執行分為兩級: 輕量級寫入使用應用程式範圍的 CoroutineScope (appScope),確保使用者離開頁面後,簡單的資料庫寫入仍能完成。 關鍵數據同步使用 Android Jetpack WorkManager。WorkManager 將任務委託給作業系統,即使應用程式被強制關閉或進入省電模式,OS 也會在滿足條件(如連上 Wi-Fi)時重新執行,確保醫療級數據不會遺失。
衝突解決與回滾
當 WorkManager 同步失敗(例如伺服器回傳 409 衝突)時,系統會將該筆本地變更標記為 FAILED,而非陷入無限重試。Repository 會透過 SharedFlow 發送一個瞬時事件(Transient Event)通知 UI,同時從隊列中清除該筆錯誤數據。由於 UI 是響應式訂閱,資料庫的變更會觸發新發射,UI 會自動回滾到最後一次伺服器確認的正確狀態。
提升測試效率:TestExtensions 模式
由於 Repository 介面為了安全性不對外暴露寫入方法,測試時難以準備數據。RDLA 引入了 TestExtensions 介面:
在 API 模組定義一個僅供測試使用的介面,包含 seedLocalHeartRates 等方法。 Repository 實作該介面,但僅在測試環境中被注入。 使用 Robolectric 搭配真實的 Room 資料庫進行單元測試。這樣可以驗證真實的 SQL 查詢,而不需要使用脆弱的 SQLite Mock,且能輕鬆模擬網路失敗來驗證離線回退邏輯。
總結
RDLA 透過將數據定義(API)與數據來源(Implementation)嚴格分離,解決了行動端開發中常見的樣板冗餘與同步混亂。它讓 ViewModel 回歸到純粹的狀態轉換角色,並利用 WorkManager 與 Room 的響應式特性,構建出真正可靠、離線優先的用戶體驗。
來源:infoq.com - Beyond CLEAN and MVP: Architecting an Offline-first Reactive Data Layer in Android
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。