直接在 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