直接在 service 裡寫 SQL 有多難測試?
你要測「訂單金額超過 1000 元免運費」這條規則,但 service 的第一行是 db.query('SELECT * FROM orders WHERE id = ?', [orderId])——為了跑這個測試,你得先準備一個資料庫,建 schema,塞測試資料,然後在 test teardown 清掉它。一個業務規則的測試,帶著整個資料庫一起走。
這是 Repository Pattern 解決的核心問題:把「如何存取資料」從業務邏輯裡分離出去,讓業務層只看到一個介面,不知道背後是 PostgreSQL、MongoDB、記憶體陣列還是 API 呼叫。
Repository Pattern 的結構
Repository 是一個介面,定義「你需要什麼樣的資料操作」,不定義「怎麼做到」:
// 介面(在 domain 層定義)
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>
findByUserId(userId: UserId): Promise<Order[]>
save(order: Order): Promise<void>
delete(orderId: OrderId): Promise<void>
}
// 實作(在 infrastructure 層)
class PostgresOrderRepository implements OrderRepository {
findById(id: OrderId) { /* SQL: SELECT ... FROM orders WHERE id = ? */ }
save(order: Order) { /* SQL: INSERT OR UPDATE ... */ }
// ...
}
// 測試用的假實作
class InMemoryOrderRepository implements OrderRepository {
private store = new Map<string, Order>()
findById(id: OrderId) { return Promise.resolve(this.store.get(id.value) ?? null) }
save(order: Order) { this.store.set(order.id.value, order); return Promise.resolve() }
// ...
}
業務邏輯拿到的是 OrderRepository 介面,不是 PostgresOrderRepository。測試時換成 InMemoryOrderRepository,業務邏輯完全不知情,也不需要知情。
真正的價值:測試變得可能
Repository Pattern 最常被提到的優點是「換資料庫不動業務邏輯」——這是真的,但換資料庫是一年一次的事,測試是每天的事。
Repository 介面讓業務邏輯的單元測試可以脫離真實資料庫。這不只是速度的問題(雖然用 mock 的測試快 10–100 倍),而是測試的隔離性:你測的是「訂單金額規則」,不是「PostgreSQL 連線是否正常」。前者是業務邏輯測試,後者是整合測試,兩件事應該分開。
這也是為什麼 Repository Pattern 和 Clean Architecture 幾乎總是一起出現——Clean Architecture 的依賴原則要求 domain 層不依賴 infrastructure 層,Repository 介面正是這個邊界的實作方式:介面定義在 domain 層,實作放在 infrastructure 層,依賴方向對了。
Repository 跟 ORM 的關係
一個常見問題:「我已經用 ORM(Sequelize / SQLAlchemy / GORM)了,還需要 Repository?」
ORM 提供的是物件-關聯映射,不是業務邊界抽象。ActiveRecord 風格的 ORM(Rails、Laravel Eloquent)讓 model 直接有 User.find(id)——這很方便,但 model 現在直接耦合了資料庫操作,測試時你又繞不開資料庫。
Repository Pattern 可以建在 ORM 之上:PostgresOrderRepository 的實作裡用 ORM 操作資料庫,但對外的介面仍然是 OrderRepository,業務層不直接碰 ORM。這讓你同時擁有 ORM 的便利和 Repository 的可測試性。
什麼時候不需要 Repository
Repository Pattern 有成本:每個 Entity 都要一個 Repository 介面 + 至少一個實作,對中小型 CRUD 系統來說這是繁文縟節。
如果你的系統以下兩點都成立,直接用 ORM 更快:
- 業務邏輯簡單(主要是 CRUD,沒有複雜的 domain rule)
- 測試策略接受整合測試(用 test DB、transaction rollback)
Repository Pattern 的回報在業務邏輯複雜、測試覆蓋率要求高的系統裡才明顯。強行把它套在 5 個 CRUD endpoint 上,是 Clean Architecture 最常見的過度工程形式之一。
下一篇 → Unit of Work Pattern
上一篇 → Clean Architecture