iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Software Development

AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流系列 第 18 篇

Day 18 - Repository 到底在幹嘛?只是幫ViewModel包一層DAO嗎?

  • 分享至 

  • xImage
  •  

前幾天一直在看 State 和 Lifecycle,今天想先換個方向,回頭看以前自己寫 Android 時很自然就放進架構裡的一層:

Repository。

以前寫 MVVM,很容易整理出這樣的結構:

Fragment
↓
ViewModel
↓
Repository
↓
DAO
↓
Room

看起來很合理,也是很常見的分層方式。

但如果真的打開 Repository,卻有可能看到這種 Code:

class RecordRepository(
    private val dao: RecordDao
) {

    fun getRecords() = dao.getRecords()

    suspend fun insert(record: Record) {
        dao.insert(record)
    }

    suspend fun delete(record: Record) {
        dao.delete(record)
    }
}

看完會出現一個很直接的問題:

如果 Repository 只是把 DAO 的 Function 再寫一次,那我為什麼不直接在 ViewModel 用 DAO?

多一層,不代表真的有分層

假設 ViewModel 原本是:

repository.getRecords()

Repository 裡面則是:

dao.getRecords()

最後其實只是:

ViewModel
↓
Repository.getRecords()
↓
DAO.getRecords()

Repository 沒有做任何判斷,也沒有隱藏什麼複雜度。

如果只是為了讓架構長成:

UI
↓
ViewModel
↓
Repository
↓
Data Source

就機械式地多包一層,那這層 abstraction 的價值確實值得懷疑。

所以 Repository 的意義應該不是:

ViewModel 和 DAO 中間一定要隔一個 Class。

而是要看:

它到底替上層隱藏了什麼?

當資料只有 Room 時,Repository 很容易看起來很多餘

像自己的 Timer Project,紀錄主要存在 Room。

如果需求很單純:

我要所有紀錄
↓
Room

那 Repository 很可能真的只剩:

fun getRecords() = dao.getRecords()

這時候它的價值並不明顯。

但假設之後資料來源開始變成:

Room
API
Cache

問題就不一樣了。

ViewModel 想要的其實只是:

我要目前的紀錄。

它不應該每次都要知道:

先讀 Room 嗎?

還是先打 API?

API 成功後要不要寫回 Room?

沒有網路時要用哪份?

Cache 過期了嗎?

這些已經不是「畫面怎麼顯示」的問題,而是:

資料應該怎麼取得。

這時 Repository 就開始有比較明確的責任。

Repository 隱藏的是「資料取得策略」

假設今天有:

          ┌→ API
ViewModel → Repository
          └→ Room

ViewModel 可以只知道:

repository.getRecords()

至於 Repository 裡面可能決定:

先顯示 Room 資料
↓
背景取得最新資料
↓
寫回 Room
↓
Room 更新
↓
UI 收到新資料

ViewModel 不需要知道這些細節。

如果未來資料來源從:

Room

變成:

Room + API

ViewModel 也不一定需要跟著大改。

所以 Repository 真正隔開的其實不是:

ViewModel
和
DAO

而是:

上層想取得什麼資料
和
底層到底怎麼取得資料

DAO 和 Repository 回答的問題不太一樣

這樣看之後,我覺得 DAO 和 Repository 可以用兩個問題區分。

DAO 比較像是在回答:

我要怎麼操作這個 Database?

例如:

@Query("SELECT * FROM record")
fun getRecords(): Flow<List<RecordEntity>>

@Insert
suspend fun insert(record: RecordEntity)

它知道的是:

Table
Query
Insert
Delete
Entity

但 Repository 比較像是在回答:

App 要怎麼取得 Record?

它可能從 Room 取得,也可能從 API 取得,甚至可能同時處理兩邊。

所以:

DAO
→ Database 怎麼操作

Repository
→ Data 怎麼取得

這兩層處理的問題其實不完全一樣。

Repository 也可以隔開資料格式

還有另一個差異。

DAO 可能回傳:

RecordEntity

API 可能回傳:

RecordResponse

但上層真正想使用的可能是:

Record

如果 ViewModel 自己開始處理:

RecordEntity
RecordResponse
API 欄位
Database 欄位

它就會慢慢知道太多 Data Layer 的細節。

這時也可以變成:

Room
↓
RecordEntity
↓
Repository
↓
Record
↓
ViewModel

或:

API
↓
RecordResponse
↓
Repository
↓
Record
↓
ViewModel

上層只面對它真正需要的資料。

這樣未來 Database Schema 或 API Response 改變時,影響範圍也比較容易被限制住。

但 Repository 不是什麼都往裡面塞

看到這裡又很容易走向另一個極端:

既然 Repository 是 Data Layer,那所有邏輯都丟進 Repository。

例如:

資料取得
時間格式化
Button 要不要 Enable
Dialog 要不要出現
畫面文字
Navigation

全部塞進去。

這當然也不對。

Repository 的存在不是為了把 ViewModel 變薄而已。

如果某個邏輯其實是在決定:

UI 要怎麼呈現

那它就不會因為「ViewModel 太長」,自動變成 Repository 的責任。

所以判斷一段邏輯該不該進 Repository,我覺得可以先問:

這件事情是在決定「資料怎麼取得」,還是在決定「畫面怎麼呈現」?

這兩個問題的責任並不一樣。

那只有 Room 的 Project,還需要 Repository 嗎?

回到最開始的問題。

如果目前真的只有:

ViewModel
↓
Repository
↓
DAO

而 Repository 每個 Function 都只是:

return dao.xxx()

那它確實沒有展現出太多額外價值。

但我現在也不會因此直接得出:

Repository 沒用,刪掉就好。

因為還要看這個 Project 想把哪個邊界固定下來。

例如 ViewModel 如果直接依賴 DAO:

ViewModel
↓
Room DAO

代表上層直接知道:

我的資料來源就是 Room。

而:

ViewModel
↓
Repository
↓
Room

至少保留了一個邊界:

ViewModel 需要的是 Record,不需要直接關心它是不是存在 Room。

所以即使目前 Repository 很薄,它還是可能在表達一個架構上的界線。

只是:

「有邊界」跟「一定值得多一層」不能直接畫上等號。

還是要看 Project 的複雜度。

Abstraction 不是越多越乾淨

以前看到:

Fragment
↓
ViewModel
↓
Repository
↓
DAO

很容易覺得層數很完整,所以架構應該比較乾淨。

但現在回頭看,我反而會想知道:

每一層到底負責什麼?

拿掉這一層之後,上下兩層會知道什麼原本不該知道的事情?

它有沒有隱藏一個真正可能改變的細節?

如果完全回答不出來,那可能只是:

為了 abstraction 而 abstraction。

反過來,如果 Repository 真的把:

資料來源
Cache 策略
同步方式
Data Mapping

這些細節隔在 ViewModel 外面,那它存在的理由就很明確。

我現在會先問「這一層替我藏了什麼?」

所以如果今天重新看到:

ViewModel
↓
Repository
↓
DAO

我不會先問:

MVVM 是不是就應該這樣分?

而是會問:

Repository 替 ViewModel 隱藏了什麼?

可能是:

資料來自哪裡

什麼時候更新

Cache 怎麼處理

Database 和 API 怎麼同步

底層資料格式怎麼轉換

如果答案是:

目前什麼都沒有,只是把 DAO Function 再呼叫一次。

那至少我會知道:

現在這個 Repository 的主要價值可能只是建立邊界,而不是因為它本身承擔了很多邏輯。

這也讓我重新理解「分層」這件事。

架構不是:

Layer 越多
↓
越乾淨

真正重要的應該是:

每一層都有明確責任
↓
改變發生時
↓
影響可以被限制在合理範圍

一層 abstraction 值不值得存在,不是看架構圖上有沒有它,而是它到底替上層隱藏了什麼。

Repository 也是一樣。

它不應該只是因為「MVVM 好像都會有」才存在,而是當資料來源和取得方式開始變複雜時,上層仍然可以只專心問:

我要什麼資料?

至於資料到底從哪裡來,才是 Repository 真正開始有價值的地方。


上一篇
Day 17 - Process Death之後,真的要把整個畫面State存起來嗎?
下一篇
Day 19 - 都是launch,為什麼Coroutine放在哪個Scope差這麼多?
系列文
AI 時代的 Android Engineer:30 天打造我的 AI 開發工作流 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言