1. 程式人生 > >應用程式框架實戰二十一:DDD分層架構之倉儲(介紹篇)

應用程式框架實戰二十一:DDD分層架構之倉儲(介紹篇)

  前面已經介紹過Entity Framework的工作單元和對映層超型別的封裝,從本文開始,將逐步介紹倉儲以及對查詢的擴充套件支援。

什麼是倉儲                                                  

  倉儲表示聚合的集合。

  倉儲所表現出來的集合外觀,僅僅是一種模擬,除了測試以外,沒有理由使用記憶體中真正的集合來建立倉儲。

  不應該為所有實體建立倉儲,只有聚合才擁有倉儲。

  倉儲用來重建已持久化的聚合,而工廠用於新建聚合。

使用倉儲的優點                                                  

  直接使用Entity Framework的DbContext不是很好嗎,為什麼還要在DbContext的上方封裝一層倉儲呢,這是否多此一舉?

  很多使用EF的程式設計師確實是直接使用DbContext,並且他們發現開發起來十分簡單,因為DbContext的介面設計得非常優雅,從介面上看,DbContext就好像所有實體集合的倉庫。

  另一方面,很多使用了倉儲的朋友,都是依葫蘆畫瓢,雖然建立了倉儲,但並沒有體會到多大好處。

  下面簡要介紹使用倉儲將為你帶來的優點。

從概念上簡化資料操作

  倉儲模擬了某種聚合的集合,而DbContext包含了N種類型的集合。

  與倉儲相近的一個數據訪問模式是資料訪問物件(DAO)模式,很多人認為倉儲不過是資料訪問物件換了一個名詞而已,從技術上說的確如此,倉儲的強大之處在於概念上更簡單。倉儲在概念上代表記憶體中的集合操作,而資料訪問物件代表資料庫操作,很明顯,集合比資料庫在概念上更簡單。

減少冗餘查詢邏輯

  如果直接使用DbContext,由於特定查詢邏輯沒有一個固定位置,可能分散到任何地方,這很容易造成冗餘。

  倉儲是封裝特定查詢邏輯的好地方,對於特定的查詢邏輯,放到與聚合相應的倉儲中即可。

降低耦合度

  直接使用DbContext,所有呼叫程式碼與EF實現高度耦合。

  另一方面,由於DbContext能夠獲取任意實體,這些實體可能位於聚合內部,這樣會破壞聚合的封裝性,同時在任意位置可以獲取任意物件,由於缺乏約束力而導致更高的耦合。

方便單元測試

  直接使用DbContext只能進行整合測試,必須連線到真實資料庫。而領域層只持有倉儲介面,所以測試時很容易替換成模擬實現,從而避開資料庫。

使用倉儲的要點                                                  

使用更具體的倉儲

  一般來講,ICustomerRepository比直接使用泛型的IRepository<Customer>要好。

  為了定義通用操作,我們會建立一些泛型基類來實現基礎操作。

  有些人發現很多具體倉儲只是直接繼承泛型倉儲,比如ICustomerRepository和CustomerRepository從泛型的IRepository<Customer>和Repository<Customer>派生,但CustomerRepository本身並沒有其它什麼程式碼。於是很多人學會偷懶,直接使用泛型基類進行操作。

  ICustomerRepository優於IRepository<Customer>的原因如下。

1.  概念上更清晰。

  ICustomerRepository從概念上良好表達了該倉儲用於操作客戶,而泛型倉儲可以操作任何聚合。

2.  為特定查詢邏輯提供唯一封裝和訪問點。

  ICustomerRepository可以將客戶相關的各種複雜查詢封裝起來,而IRepository<Customer>很難對客戶查詢進行封裝,一個辦法是使用擴充套件方法來完成封裝,不過比起CustomerRepository要麻煩得多,而且實現程式碼也更難管理。

3.  降低耦合。

  泛型IRepository<>可以在任意位置獲取任意聚合,這比起DbContext要強一些,不會破壞聚合的封裝性,但缺乏約束力仍然會導致更高耦合。

  ICustomerRepository通過明確的宣告,只能獲取需要的聚合,從而控制了物件的訪問。

倉儲僅返回與其聚合高度相關的內容

  倉儲可以返回對應的聚合,也可以返回聚合相關的統計資訊,甚至可以返回聚合的一個子集。

  有人在倉儲查詢中使用匿名物件,使用匿名物件的原因很簡單,即為了進行投影操作,這樣可以限制Sql查詢返回的列,對於Sql效能調優來講,這可能非常重要,比如使用Sql Server的覆蓋索引。

  但是使用匿名物件進行查詢有很多問題,一個問題是無法直接作為結果返回。有人為了省力,直接使用聚合或其部分子物件作為返回型別,其結果是物件中的一部分屬性被賦值,另一部分屬性為null。這可能導致許多神祕的Bug,另外導致你或其它人對倉儲查詢信心不足,因為這些查詢並不安全,你每當需要呼叫一個查詢,首先得仔細檢查程式碼,看看哪些值是空的,以免踩到地雷。

  如果要返回聚合的一個子集,需要單獨定義一些物件,以作為返回型別。工作量雖然比較大,但更加安全和清晰。

  如果需要獲取的內容由多個聚合組成,這個查詢操作應該放入哪個倉儲中?這種情況放到哪個倉儲其實都不合適,更好的辦法是使用一個查詢服務,就好像跨越多個聚合的業務操作需要領域服務一樣。

總結                                                  

1. 倉儲的定義

倉儲表示聚合的集合。

2. 倉儲的優點

  • 簡化資料操作
  • 減少冗餘查詢邏輯
  • 降低耦合度
  • 方便單元測試

3. 倉儲的要點

  • 使用更具體的倉儲
  • 倉儲查詢僅返回與其聚合高度相關的內容

  由於倉儲是對資料操作的封裝,包括倉儲基礎,分頁,查詢擴充套件,查詢物件,查詢條件(規約模式),查詢條件應用(日期範圍與數值範圍查詢)等內容,所以需要多篇文章進行介紹。

  .Net應用程式框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。