[.NET][Design Patterns] 無辜的 Repository Pattern

因為最近分層架構的流行 (拜三隻豬之賜?),愈來愈多人談論起 Repository 的設計,也開始有人認為 Repository 無用,說實在的,Repository 有用於否,存乎一心,當你認為它有用時,隨手寫了它也不會覺得奇怪,但如果一開始就認為它是多餘的,就算人家給你程式產生器,你還是會認為它是多餘的。

什麼叫 Repository? 這個就叫 Repository。

(Source: http://english.nigpas.cas.cn/rs/fr/)

很多應用程式都有資料存取的需求,而且有絕大多數使用的都是關聯式資料庫 (RDBMS),長久以來,也有絕大多數的開發人員使用傳統的資料庫存取介面連結並使用資料庫,舉凡 ODBC, DB-Library, ADO, ADO.NET, OLE DB 等不同的介面,再搭配 SQL 指令 (不管是 PL/SQL, Transact-SQL, ANSI SQL 或是 Jet SQL) 來操作資料,大多數的開發人員也很習慣這一點,事實上,小型的應用程式通常只有一種資料來源:資料庫,鮮少會有一個應用程式有多種資料儲存的介面,但是現在隨著應用程式的雲端化、分散化與多樣性,應用程式的資料來源再也不只一種,例如使用 NoSQL、經由訊息介面 (Messaging) 作通訊、接取資訊進行處理等工作,使得應用程式要面對的資料類型愈來愈多,若是再繼續使用傳統的作法 (將應用程式邏輯與資料存取介面緊密結合),將會使得應用程式在面對資料時受限,影響應用程式的發展性。

為了要整合這麼多種資料儲存類型,最好的方法就是將要儲存的物件抽取出來變成一個物件實體,這個基本上就是三隻豬設計法 (Domain Driven Design, DDD) 裡面所稱的 Entity (當然 Value Object 也可以存在資料儲存體),你也可以稱它為領域物件 (Domain Object),有了領域物件後,就可以試著將對資料儲存體互動的介面抽象化,並傳遞領域物件到這個介面,以達到儲存或擷取領域物件的能力,這個與資料儲存體互動的抽象化介面,就是大家經常看到的 Repository。

Martin Fowler 給的定義也很貼切:

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. (為存取領域物件,使用類似集合的介面在領域與資料對應層的協調者)

也就是說,Repository 其實是存取資料的抽象化 API,只要是向資料儲存體放資料或要資料的功能,都會經由它來處理,有了抽象化的 API 後,上層的服務 (Service) 只要關心 Repository 的抽象化 API,至於它的實作? 只要由真正負責執行資料存取的程式碼實作,並搭配 Dependency Injection (DI) 技術於適當的時候注入到服務內即可,如果資料儲存體換了,只要抽換掉 Repository API 的實作即可,上層的服務完全不需要異動。而這也是 Repository 最終的目的。

Repository 有時候也會搭配 Unit of Work,將對資料儲存操作的多種作業以交易 (Transaction) 的方式處理,不過 Unit of Work 會需要與交易相關的抽象化,所以其實不容易作,或是要搭配具交易控制能力的資料儲存 API 使用。 

實作 Repository 最快的方法,目前公認是 ORM (Object Relational Mapping) 技術,而且現在已經有很多實作品,例如 Entity Framework、Hibernate 等實作,可有效率的將物件與資料庫的存取連動操作,再加上大多數的資料操作都是以 CRUD 居多,造就了 Generic Repository 的實作,例如:

// source: https://code.msdn.microsoft.com/generic-repository-pattern-ddea2262
public interface IService<M, T, E> 
{ 
   M GetSingle(Expression<Func<T, bool>> whereCondition); 

   void Add(M entity); 
   void Delete(M entity); 
   void Update(M entity); 
 
   List<M> GetAll(Expression<Func<T, bool>> whereCondition); 
   List<M> GetAll(); 
 
   IQueryable<T> Query(Expression<Func<T, bool>> whereCondition); 
 
   long Count(Expression<Func<T, bool>> whereCondition); 
   long Count(); 
}

或許是因為 Generic Repository 的關係,也開始讓許多人認為 Repository 只要這樣做做就好了,或是直接拿 ORM 作為 Repository API,但是有趣的是,Generic Repository 還勉強是有一點抽象的 API,遇到需要抽換的時候還可以抽換,但直接拿 ORM 作為 Repository API,連抽換的能力都免了,也就是這篇文章所說的

Repository 真的好無辜啊,被一些人嫌到沒有存在的必要。

就我的觀點而言,如果是一個不需要抽換資料儲存體的應用程式,確實可以不實作 Repository API,連 Generic Repository 都可以省了,直接操作 ORM 或是資料儲存 API 就能做完工作,但是萬一有一天,老闆要你把 SQL Server 換成 MongoDB (採用 eventually consistent 以加速處理),或是把檔案換成資料庫,恭喜,那些資料存取的程式就準備重寫吧。

Repository (或是其他的 Design Pattern) 的存在,都是為了要解決一個特定場域的問題,或許資料儲存體沒有經常抽換的可能,但是 Repository 和 Factory, Strategy 這些 Pattern 一樣,幾乎是隨手就能實作的 Pattern,而且只要應用得當,能讓應用程式獲取更寬廣的空間,何樂而不為呢?

當在業界久了,經歷了一些事情,或許你就會了解有時候隨手做的一些動作,有一天會為你省下不少時間,而沒做的話會讓你多了一堆工作。