DDD領域驅動設計初探(2):倉儲Repository(上)

發表於2016-04-07

前言:上篇介紹了DDD設計Demo裡面的聚合劃分以及實體和聚合根的設計,這章繼續來說說DDD裡面最具爭議的話題之一的倉儲Repository,為什麼Repository會有這麼大的爭議,博主認為主要原因無非以下兩點:一是Repository的真實意圖沒有理解清楚,導致設計的紊亂,隨著專案的橫向和縱向擴充套件,到最後越來越難維護;二是趕時髦的為了“模式”而“模式”,倉儲並非適用於所有專案,這就像沒有任何一種架構能解決所有的設計難題一樣。本篇通過這個設計的Demo來談談博主對倉儲的理解,有不對的地方還望園友們斧正!

一、倉儲的定義

倉儲,顧名思義,儲存資料的倉庫。那麼有人就疑惑了,既然我們有了資料庫來存取資料,為什麼還要弄一個倉儲的概念,其實博主覺得這是一個考慮層面不同的問題,資料庫主要用於存取資料,而倉儲作用之一是用於資料的持久化。從架構層面來說,倉儲用於連線領域層和基礎結構層,領域層通過倉儲訪問儲存機制,而不用過於關心儲存機制的具體細節。按照DDD設計原則,倉儲的作用物件的領域模型的聚合根,也就是說每一個聚合都有一個單獨的倉儲。可能這樣說大家未必能理解,相信看了倉儲的程式碼設計,大家能有一個更加透徹的認識。

二、使用倉儲的意義

1、站在領域層更過關心領域邏輯的層面,上面說了,倉儲作為領域層和基礎結構層的連線元件,使得領域層不必過多的關注儲存細節。在設計時,將倉儲介面放在領域層,而將倉儲的具體實現放在基礎結構層,領域層通過介面訪問資料儲存,而不必過多的關注倉儲儲存資料的細節(也就是說領域層不必關心你用EntityFrameWork還是NHibernate來儲存資料),這樣使得領域層將更多的關注點放在領域邏輯上面。

2、站在架構的層面,倉儲解耦了領域層和ORM之間的聯絡,這一點也就是很多人設計倉儲模式的原因,比如我們要更換ORM框架,我們只需要改變倉儲的實現即可,對於領域層和倉儲的介面基本不需要做任何改變。

三、程式碼示例

1、解決方案結構圖

 

上面說了,倉儲的設計是介面和實現分離的,於是,我們的倉儲介面和工作單元介面全部放在領域層,在基礎結構層新建了一個倉儲的實現類庫ESTM.Repository,這個類庫需要新增領域層的引用,實現領域層的倉儲介面和工作單元介面。所以,通過上圖可以看到領域層的IRepositories裡面的倉儲介面和基礎結構層ESTM.Repository專案下的Repositories裡面的倉儲實現是一一對應的。下面我們來看看具體的程式碼設計。其實園子裡已有很多經典的倉儲設計,為了更好地說明倉儲的作用,博主還是來班門弄斧下了~~

2、倉儲介面

除了IRepository這個泛型介面,其他4個倉儲介面都是針對聚合建立的介面, 上章 C#進階系列——DDD領域驅動設計初探(一):聚合 介紹了聚合的劃分,這裡的倉儲介面就是基於此建立。IUserRepository介面實現了IRepository介面,並把對應的聚合根傳入泛型,這裡正好應徵了上章聚合根的設計。

3、倉儲實現類

倉儲的泛型實現類裡面通過MEF匯入工作單元,工作單元裡面擁有連線資料庫的上下文物件。

倉儲是4個具體實現類裡面也可以通過基類裡面匯入的工作單元物件UnitOfWork去運算元據庫。

4、工作單元介面

看到這兩個介面可能有人就有疑惑了,為什麼要設計兩個介面,直接合並一個不行麼?這個工作單元的設計思路來源dax.net的系列文章,再次表示感謝!的確,剛開始,博主也有這種疑惑,仔細思考才知道,應該是出於事件機制來設計的,實現IUnitOfWorkRepositoryContext這個介面的都是針對倉儲設計的工作單元,而實現IUnitOfWork這個介面除了倉儲的設計,可能還有其他情況,比如事件機制。

5、工作單元實現類

為什麼要在這裡還設計一層介面?因為博主覺得,工作單元要引入EF的Context物件,同理,如果你用的NH,那麼這裡應該是引入Session物件

工作單元EFUnitOfWork上面註冊了MEF的Export,是為了供倉儲的實現基類裡面Import,同理,這裡有一點需要注意的,這裡要想匯入DbContext,那麼EF的上下文物件就要Export

這裡用了萬能的部分類partial,還記得上章說到的領域Model麼,也是在edmx的基礎上通過部分類在定義的。同樣,在edmx的下面肯定有一個EF自動生成的上下文物件,如下:

上文中多個地方用到了註冊MEF的方法

是因為我們在基礎結構層裡面定義了註冊方法

6、Demo測試

為了測試我們搭的框架能執行通過,我們在應用層裡面寫一個測試方法。正常情況下,應用層ESTM.WCF.Service專案只需要新增ESTM.Domain專案的引用,那麼在應用層裡面如何找到倉儲的實現呢?還是我們萬能的MEF,通過IOC依賴注入的方式,應用層不必新增倉儲實現層的引用,通過MEF將倉儲實現注入到應用層裡面,但前提是應用層的bin目錄下面要有倉儲實現層生成的dll,需要設定ESTM.Repository專案的生成目錄為ESTM.WCF.Service專案的bin目錄。這個問題在C#進階系列——MEF實現設計上的“鬆耦合”(終結篇:面向介面程式設計)這篇裡面介紹過

還是來看看測試程式碼

執行得到結果:

 

7、總結

至此,我們框架倉儲的大致設計就完了,我們回過頭來看看這樣設計的優勢所在:

(1)倉儲介面層和實現層分離,使得領域模型更加純淨,領域模型只關注倉儲的介面,而不用關注資料儲存的具體細節,使得領域模型將更多的精力放在領域業務上面。

(2)應用層只需要引用領域層,只需要呼叫領域層裡面的倉儲介面就能得到想要的資料,而不用新增倉儲具體實現的引用,這也正好符合專案解耦的設計。

(3)更換ORM方便。專案現在用的是EF,若日後需要更換成NH,只需要再實現一套倉儲和上下文即可。這裡需要說明一點,由於整個框架使用EF的model First,為了直接使用EF的model,我們把edmx定義在了領域層裡面,其實這樣是不合理的,但是我們為了使用簡單,直接用了partial定義領域模型的行為,如果要更好的使用DDD的設計,EF現在的Code First是最好的方式,領域層裡面只定義領域模型和關注領域邏輯,EF的CRUD放在基礎結構層,切換ORM就真的只需要重新實現一套倉儲即可,這樣的設計才是博主真正想要的效果,奈何時間和經歷有限,敬請諒解。以後如果有時間博主會分享一個完整設計的DDD。

DDD領域驅動設計初探系列文章:

相關文章