天天看點

C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)

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

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

<a href="http://www.cnblogs.com/landeanfen/p/4816706.html">C#進階系列——DDD領域驅動設計初探(一):聚合</a>

<a href="http://www.cnblogs.com/landeanfen/p/4834730.html">C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)</a>

<a href="http://www.cnblogs.com/landeanfen/p/4837520.html">C#進階系列——DDD領域驅動設計初探(三):倉儲Repository(下)</a>

<a href="http://www.cnblogs.com/landeanfen/p/4841211.html">C#進階系列——DDD領域驅動設計初探(四):WCF搭建</a>

<a href="http://www.cnblogs.com/landeanfen/p/4842015.html">C#進階系列——DDD領域驅動設計初探(五):AutoMapper使用</a>

<a href="http://www.cnblogs.com/landeanfen/p/4844344.html" target="_blank">C#進階系列——DDD領域驅動設計初探(六):領域服務</a>

<a href="http://www.cnblogs.com/landeanfen/p/4920577.html" target="_blank">C#進階系列——DDD領域驅動設計初探(七):Web層的搭建</a>

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

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

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

C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)

上面說了,倉儲的設計是接口和實作分離的,于是,我們的倉儲接口和工作單元接口全部放在領域層,在基礎結構層建立了一個倉儲的實作類庫ESTM.Repository,這個類庫需要添加領域層的引用,實作領域層的倉儲接口和工作單元接口。是以,通過上圖可以看到領域層的IRepositories裡面的倉儲接口和基礎結構層ESTM.Repository項目下的Repositories裡面的倉儲實作是一一對應的。下面我們來看看具體的代碼設計。其實園子裡已有很多經典的倉儲設計,為了更好地說明倉儲的作用,部落客還是來班門弄斧下了~~

倉儲的泛型實作類裡面通過MEF導入工作單元,工作單元裡面擁有連接配接資料庫的上下文對象。

倉儲是4個具體實作類裡面也可以通過基類裡面導入的工作單元對象UnitOfWork去操作資料庫。

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

為什麼要在這裡還設計一層接口?因為部落客覺得,工作單元要引入EF的Context對象,同理,如果你用的NH,那麼這裡應該是引入Session對象。

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

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

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

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

C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)

還是來看看測試代碼

 運作得到結果:

C#進階系列——DDD領域驅動設計初探(二):倉儲Repository(上)

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

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

(2)應用層隻需要引用領域層,隻需要調用領域層裡面的倉儲接口就能得到想要的資料,而不用添加倉儲具體實作的引用,這也正好符合項目解耦的設計。

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