C#进阶系列——DDD领域驱动设计初探(一):聚合
前言:又有差不多半個(gè)月沒寫點(diǎn)什么了,感覺這樣很對(duì)不起自己似的。今天看到一篇博文里面寫道:越是忙人越有時(shí)間寫博客。呵呵,似乎有點(diǎn)道理,博主為了證明自己也是忙人,這不就來學(xué)習(xí)下DDD這么一個(gè)聽上去高大上的東西。前面介紹了下MEF和AOP的相關(guān)知識(shí),后面打算分享Automapper、倉儲(chǔ)模式、WCF等東西的,可是每次準(zhǔn)備動(dòng)手寫點(diǎn)什么的時(shí)候,就被要寫的Demo難住了,比如倉儲(chǔ)模式,使用過它的朋友應(yīng)該知道,如果你的項(xiàng)目不是按照DDD的架構(gòu)而引入倉儲(chǔ)的設(shè)計(jì),那么會(huì)讓它變得很“雞肋”,用不好就會(huì)十分痛苦,之前看過這篇博客園的大牛們,被你們害慘了,Entity Framework從來都不需要去寫Repository設(shè)計(jì)模式文章的朋友應(yīng)該還記得,不止是該文章的作者,很多園友在評(píng)論里面也提到了使用它的不爽。博主的項(xiàng)目中也遇到類似的問題,雖然引入了倉儲(chǔ)模式,但是由于沒有架構(gòu)好,把倉儲(chǔ)的接口和實(shí)現(xiàn)統(tǒng)一放在了數(shù)據(jù)訪問層,導(dǎo)致到后面代碼越寫越難維護(hù),完全感覺不到倉儲(chǔ)帶來的好處。所以博主覺得單純分享倉儲(chǔ)模式很容易使讀者陷入“為了模式而模式”的誤區(qū),再加上最近一段時(shí)間在看《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道.Eric.Eva》這本書和博客園大牛dax.net的DDD系列文章,所以打算分享一個(gè)Demo來說明倉儲(chǔ)模式、Automapper、WCF等知識(shí)點(diǎn)。
DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探系列文章:
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(一):聚合
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(二):倉儲(chǔ)Repository(上)
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(三):倉儲(chǔ)Repository(下)
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(四):WCF搭建
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(五):AutoMapper使用
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(六):領(lǐng)域服務(wù)
C#進(jìn)階系列——DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)初探(七):Web層的搭建
一、領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)基本概念
根據(jù)《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道.Eric.Eva》書中的觀點(diǎn),領(lǐng)域模型是軟件項(xiàng)目的公共語言的核心,是領(lǐng)域?qū)<液烷_發(fā)人員共同遵守的通用語言規(guī)則,那么在DDD里面,建模的重要性不用多說,所以要想更好理解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),理解領(lǐng)域模型的劃分和建立就變得相當(dāng)必要。首先來看看DDD里面幾個(gè)比較重要的概念:
1、領(lǐng)域模型:領(lǐng)域模型與數(shù)據(jù)模型不同,它表述的是領(lǐng)域中各個(gè)類及其之間的關(guān)系。從領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的角度看,數(shù)據(jù)庫只不過是存儲(chǔ)實(shí)體的一個(gè)外部機(jī)制,是屬于技術(shù)層面的東西。數(shù)據(jù)模型主要用于描述領(lǐng)域模型對(duì)象的持久化方式,應(yīng)該是先有領(lǐng)域模型,才有數(shù)據(jù)模型,領(lǐng)域模型需要通過某種映射而產(chǎn)生相應(yīng)的數(shù)據(jù)模型,從這點(diǎn)來說,最新的EF的Code First就是一個(gè)很好的體現(xiàn)。領(lǐng)域模型對(duì)象分為實(shí)體、值對(duì)象和服務(wù)。
2、實(shí)體:在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)里面,實(shí)體是模型中需要區(qū)分個(gè)體的對(duì)象。這里的實(shí)體和EntityFramework里面的實(shí)體不是一個(gè)概念,EF的實(shí)體為數(shù)據(jù)實(shí)體,不包含對(duì)象的行為。就博主的理解,DDD概念里面的實(shí)體就是包括實(shí)體數(shù)據(jù)(EF的Model)和行為的結(jié)合體。
3、值對(duì)象:通過對(duì)象屬性值來識(shí)別的對(duì)象,它將多個(gè)相關(guān)屬性組合為一個(gè)概念整體。相比實(shí)體而言,值對(duì)象僅僅是比實(shí)體少了一個(gè)標(biāo)識(shí)。值對(duì)象的設(shè)計(jì)比較存在爭議,我們暫且記住值對(duì)象和實(shí)體的區(qū)別:(1)實(shí)體擁有唯一標(biāo)識(shí),而值對(duì)象沒有;(2)實(shí)體允許變化,而值對(duì)象不允許變化;(3)判斷兩個(gè)實(shí)體相等的方法是判斷實(shí)體的標(biāo)識(shí)相等,而判斷兩個(gè)值對(duì)象相等的標(biāo)準(zhǔn)是值對(duì)象內(nèi)部所有屬性值相等;
4、聚合(以及聚合根):聚合表示一組領(lǐng)域?qū)ο?包括實(shí)體和值對(duì)象),用來表述一個(gè)完整的領(lǐng)域概念。而每個(gè)聚合都有一個(gè)根實(shí)體,這個(gè)根實(shí)體又叫做聚合根。舉個(gè)簡單的例子,一個(gè)電腦包含硬盤、CPU、內(nèi)存條等,這一個(gè)組合就是一個(gè)聚合,而電腦就是這個(gè)組合的聚合根。博主覺得關(guān)于聚合的劃分學(xué)問還是挺大的,需要在實(shí)踐中慢慢積累。同一個(gè)實(shí)體,在不同的聚合中,它可能是聚合根,也可能不是,需要根據(jù)實(shí)際的業(yè)務(wù)決定。聚合根是聚合所表述的領(lǐng)域概念的主體,外部對(duì)象需要訪問聚合內(nèi)的實(shí)體時(shí),只能通過聚合根進(jìn)行訪問,而不能直接訪問。
5、領(lǐng)域服務(wù):博主的理解,領(lǐng)域模型主張富領(lǐng)域模式,也就是說把領(lǐng)域邏輯盡量寫在領(lǐng)域?qū)嶓w里面,也就是常說的“充血模式”,而對(duì)于業(yè)務(wù)邏輯,最好是以服務(wù)的形式提供。至于領(lǐng)域邏輯和業(yè)務(wù)邏輯的界定,這個(gè)要根據(jù)實(shí)際情況來定。總之,領(lǐng)域服務(wù)是用來處理那些領(lǐng)域模型里面不好定義或者某些可變邏輯的的時(shí)候才會(huì)用到。待驗(yàn)證!
6、工廠、倉儲(chǔ)等概念留在Demo里面說明。
二、領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)開始之旅
1、項(xiàng)目分層
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)將軟件系統(tǒng)分為四層:基礎(chǔ)結(jié)構(gòu)層、領(lǐng)域?qū)印?yīng)用層和表現(xiàn)層。來看看書中的分層:
其實(shí)在dax.net的系列中這張圖更能說明這種架構(gòu)
2、項(xiàng)目架構(gòu)
博主打算用權(quán)限系統(tǒng)的案例說明的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的項(xiàng)目架構(gòu)。項(xiàng)目嚴(yán)格按照表現(xiàn)層、應(yīng)用層、領(lǐng)域?qū)印⒒A(chǔ)設(shè)施層來劃分。
表現(xiàn)層:MVC的Web項(xiàng)目,負(fù)責(zé)UI呈現(xiàn)。
應(yīng)用層:WCF服務(wù),負(fù)責(zé)協(xié)調(diào)領(lǐng)域?qū)拥恼{(diào)用,向UI層提供需要的接口。
領(lǐng)域?qū)樱憾x領(lǐng)域?qū)嶓w和領(lǐng)域邏輯。
基礎(chǔ)設(shè)施層:一些通用的技術(shù),比如AOP、MEF注入、通用的工具類、DTO模型層,這里為什么要有一個(gè)DTO模型層,DTO是用于UI展現(xiàn)用的純數(shù)據(jù)Model,它不包含實(shí)體行為,是一種貧血的模型。
整個(gè)項(xiàng)目的調(diào)用方式嚴(yán)格按照DDD設(shè)計(jì)來進(jìn)行,UI層通過WCF服務(wù)調(diào)用應(yīng)用層的WCF接口,WCF服務(wù)通過倉儲(chǔ)調(diào)用領(lǐng)域?qū)永锩娴慕涌冢A(chǔ)設(shè)施層貫穿其他各層,在需要的項(xiàng)目中都可以引用基礎(chǔ)設(shè)施層里面的內(nèi)庫。
3、代碼示例
接下來,博主就根據(jù)自己的理解,從零開始使用這種架構(gòu)寫一個(gè)簡單的權(quán)限管理系統(tǒng)。由于是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),所以,文章的重點(diǎn)會(huì)放在領(lǐng)域?qū)樱?xiàng)目使用了EF的Model First來進(jìn)行,先設(shè)計(jì)實(shí)體,后生成數(shù)據(jù)庫。
3.1 首先來看看表結(jié)構(gòu)
根據(jù)博友要求,這里說明一下表之間的映射關(guān)系:
1表示TB_DEPARTMENT表的主鍵DEPARTMENT_ID作為TB_USERS表的外鍵;
2表示TB_USERS表的主鍵USER_ID作為TB_USERROLE表的外鍵;
3表示TB_ROLE表的主鍵ROLE_ID作為TB_USERROLE表的外鍵;
4表示TB_ROLE表的主鍵ROLE_ID作為TB_MENUROLE表的外鍵
5表示TB_MENU表的主鍵MENU_ID作為TB_MENUROLE表的外鍵
首先建好對(duì)應(yīng)的表實(shí)體,然后根據(jù)模型生成數(shù)據(jù)庫
將生成的sql語句執(zhí)行后就可以得到對(duì)應(yīng)的表結(jié)構(gòu)。
3.2 聚合的劃分
在領(lǐng)域?qū)永锩嫖覀冃陆ㄒ粋€(gè)BaseModel,里面有三個(gè)類
這三個(gè)類IEntity、IAggregateRoot、AggregateRoot分別定義了實(shí)體的接口、聚合根的接口、聚合根的抽象實(shí)現(xiàn)類。
//用作泛型約束,表示繼承自該接口的為領(lǐng)域?qū)嶓w
public interface IEntity
{
}
/// <summary>
/// 聚合根接口,用作泛型約束,約束領(lǐng)域?qū)嶓w為聚合根,表示實(shí)現(xiàn)了該接口的為聚合根實(shí)例,由于聚合根也是領(lǐng)域?qū)嶓w的一種,所以也要實(shí)現(xiàn)IEntity接口
/// </summary>
public interface IAggregateRoot:IEntity
{
}
/// <summary>
/// 聚合根的抽象實(shí)現(xiàn)類,定義聚合根的公共屬性和行為
/// </summary>
public abstract class AggregateRoot:IAggregateRoot
{
}
這里定義接口的作用是定義實(shí)體和聚合根的泛型約束,抽象類用來定義聚合根的公共行為,目前為止,這些接口和類里面都是空的,后面會(huì)根據(jù)項(xiàng)目的需求一步一步往里面加入邏輯。
在EF里面由edmx文件會(huì)生成實(shí)體的屬性,前面說到領(lǐng)域模型主張充血模式,所以要在EF的實(shí)體model里面加入實(shí)體的行為,為了不改變EF生成實(shí)體的代碼,我們使用partial類來定義實(shí)體的行為。我們來看Model文件夾下面的代碼
public partial class TB_DEPARTMENT: AggregateRoot
{
public override string ToString()
{
return base.ToString();
}
}
public partial class TB_MENU : AggregateRoot
{
}
/// <summary>
/// 由于不會(huì)直接操作此表,所以TB_MENUROLE實(shí)體不必作為聚合根,只是作為領(lǐng)域?qū)嶓w即可
/// </summary>
public partial class TB_MENUROLE:IEntity
{
}
public partial class TB_ROLE:AggregateRoot
{
}
public partial class TB_USERROLE:IEntity
{
}
public partial class TB_USERS:AggregateRoot
{
}
我們看到,這些實(shí)體,只有TB_MENUROLE和TB_USERROLE不是聚合根,其他實(shí)體都是聚合根。我這里大概劃分為4個(gè)聚合:
聚合1:TB_DEPARTMENT實(shí)體
聚合2:TB_MENU、TB_MENUROLE、TB_ROLE這3個(gè)為一個(gè)聚合,聚合根是TB_MENU。
聚合3:TB_USERS、TB_USERROLE、TB_DEPARTMENT、TB_ROLE這4個(gè)為一個(gè)聚合,聚合根是TB_USERS。
聚合4:TB_ROLE、TB_USERS、TB_USERROLE、TB_MENUROLE、TB_MENU這5個(gè)表為一個(gè)聚合,聚合根是TB_ROLE。
可能這樣分會(huì)有一定的問題,后續(xù)出現(xiàn)再慢慢糾正。
到這里,聚合的劃分基本完了,至于為什么要做這么一些約束和設(shè)計(jì),因?yàn)閭}儲(chǔ)只能對(duì)聚合根做操作,下篇講倉儲(chǔ)的時(shí)候會(huì)詳細(xì)說明。
DDD博大精深,文中很多觀點(diǎn)為博主個(gè)人理解,可能不太成熟或者有誤,歡迎拍磚~~
總結(jié)
以上是生活随笔為你收集整理的C#进阶系列——DDD领域驱动设计初探(一):聚合的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 本命年戴什么转运(本命年右手必须戴银?)
- 下一篇: 软件的版本控制