成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

初探領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):為復(fù)雜業(yè)務(wù)而生

開(kāi)發(fā)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)也就是3D(Domain-Driven Design)已經(jīng)有了10年的歷史,我相信很多人或多或少都聽(tīng)說(shuō)過(guò)這個(gè)名詞,但是有多少人真正懂得如何去運(yùn)用它,或者把它運(yùn)用好呢?于是有人說(shuō),DDD和TDD這些玩意是一些形而上的東西,只是一茶余飯后的談資,又或是放到簡(jiǎn)歷上提升逼格而已。

概述

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)也就是3D(Domain-Driven Design)已經(jīng)有了10年的歷史,我相信很多人或多或少都聽(tīng)說(shuō)過(guò)這個(gè)名詞,但是有多少人真正懂得如何去運(yùn)用它,或者把它運(yùn)用好呢?于是有人說(shuō),DDD和TDD這些玩意是一些形而上的東西,只是一茶余飯后的談資,又或是放到簡(jiǎn)歷上提升逼格而已。前面這句話(huà)我寫(xiě)完之后猶豫了,猶豫要不要把它刪掉,因?yàn)樗屛铱雌饋?lái)像個(gè)噴子,我確實(shí)感到不解,為什么別人10年前創(chuàng)造總結(jié)出來(lái)的東西,我們?cè)?0年之后對(duì)它的理解還處于這么低的一個(gè)層次。開(kāi)篇就說(shuō)遠(yuǎn)了,我也是最近才開(kāi)始認(rèn)真學(xué)習(xí)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),并且得到了園子里面netfocus,劉標(biāo)才田園里的蟋蟀的幫助,在此再次表示感謝。希望能和大家一起把DDD普及下去。

我們之前有一個(gè)關(guān)于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的討論,另外dax.net也有一個(gè)關(guān)于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的系列寫(xiě)得不錯(cuò),有興趣的同學(xué)可以看看。本文會(huì)以一個(gè)初學(xué)者的角度來(lái)講解DDD,讓我們一切從零開(kāi)始,我相信你跟我一樣也會(huì)愛(ài)上它的。

本篇主要討論一下為什么我們要用DDD,它能夠?yàn)槲覀儙?lái)什么?

一點(diǎn)廢話(huà) ,我們需要好的設(shè)計(jì)么?

當(dāng)我們學(xué)習(xí)一些設(shè)計(jì)模式或者框架的時(shí)候,總有人會(huì)站出來(lái)和你說(shuō)“這些都沒(méi)有用,只要能實(shí)現(xiàn)功能就行了。” 在這里并非針對(duì)某個(gè)人,實(shí)際上我認(rèn)為他們說(shuō)的是對(duì)的,在資源有限的情況下,我們?yōu)榱送瓿身?xiàng)目的交付,這是我們最好的選擇。但是別忘了,欠下的債總是要還的,以實(shí)現(xiàn)功能為導(dǎo)向的項(xiàng)目務(wù)必會(huì)造成維護(hù)性的大大降低,如果只是一個(gè)臨時(shí)隨便用用的東西倒是可以一試,但如果是要長(zhǎng)期進(jìn)行更新的產(chǎn)品,那后期就會(huì)拖該產(chǎn)品的后腿。

我們團(tuán)隊(duì)現(xiàn)在維護(hù)著一個(gè)有著20多年歷史的產(chǎn)品,該產(chǎn)品是一個(gè)酒店、餐飲行業(yè)的POS系統(tǒng),在美國(guó)和亞太地區(qū)都占有著比較大的市場(chǎng)份額。該產(chǎn)品從C,C++,VB6一路更新,直到現(xiàn)在的C#,但是很可惜不是整體替換,而是局部的,所以現(xiàn)在項(xiàng)目里面這4種代碼全都有。可能你會(huì)覺(jué)得這玩的是混搭,是潮流,但事實(shí)是,一旦產(chǎn)品上線(xiàn)之后,會(huì)有很多的新功能,老bug等在那里,再加上“重市場(chǎng)輕技術(shù)”的高層在那里制訂戰(zhàn)略,你壓根就沒(méi)有時(shí)間或者沒(méi)有多少時(shí)間去重構(gòu)。日積月累,等著你的就是每一次改代碼都如履薄冰,一不小心就因?yàn)楦囊粋€(gè)bug而整出好幾個(gè)新bug出來(lái),前不久我們?yōu)榱诵掳姹镜陌l(fā)布就停下所有開(kāi)發(fā)的任務(wù),大家集體花了1個(gè)月的時(shí)間去做回歸測(cè)試了。因?yàn)榍捌诎l(fā)布新版本之后bug太多,所以這次老大們都不敢輕易發(fā)布了。:)

這是我們血的教訓(xùn),如果你前期只顧開(kāi)發(fā)功能,最后就會(huì)讓你很難再開(kāi)發(fā)新功能。所以真誠(chéng)的希望大家不要再片面的說(shuō)“只要實(shí)現(xiàn)功能就可以了!”,軟件開(kāi)發(fā)的領(lǐng)域這么大,我們沒(méi)有必要把自己局限在某一個(gè)框框里面。對(duì)于大型系統(tǒng)來(lái)說(shuō),我們要學(xué)習(xí)的地方還有很多:

  • 組織良好、可閱讀性高的代碼可以讓其它開(kāi)發(fā)人員很容易的開(kāi)始去修改代碼。
  • 低耦合,高內(nèi)聚 - 適合運(yùn)用設(shè)計(jì)模式以及原則來(lái)設(shè)計(jì)一些好的框架可以降低修改代碼引發(fā)新bug的風(fēng)險(xiǎn)。
  • 良好的單元測(cè)試以及集成測(cè)試可以及時(shí)的幫助我們檢測(cè)新增或修改的代碼是否會(huì)破壞原有的邏輯。
  • 自動(dòng)化測(cè)試絕對(duì)是省時(shí)省力的好幫手,也是項(xiàng)目質(zhì)量的保證。
  • 持續(xù)集成可以幫助我們更快速安全的進(jìn)行迭代。

上面說(shuō)了這么多也沒(méi)有提到DDD,那么為什么它能夠在構(gòu)建復(fù)雜系統(tǒng)的時(shí)候有優(yōu)勢(shì)呢?我們可以從以下幾個(gè)點(diǎn)去思考:

  • 從設(shè)計(jì)階段出發(fā),站在業(yè)務(wù)的角度思考問(wèn)題
  • 厘清業(yè)務(wù)主次
  • 獨(dú)立領(lǐng)域業(yè)務(wù)層,打通開(kāi)發(fā)和測(cè)試階段
  • 干凈的代碼

從設(shè)計(jì)階段開(kāi)始,站在業(yè)務(wù)的角度思考問(wèn)題

除了DDD,現(xiàn)在還流行另外一個(gè)詞匯TDD。但是不知道大家有沒(méi)有注意到DDD(Domain-Driven Design)中的D代表著設(shè)計(jì),而TDD(Test-Driven Development)中的D代表著開(kāi)發(fā),你有沒(méi)有曾幾何時(shí)把領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)說(shuō)成領(lǐng)域驅(qū)動(dòng)開(kāi)發(fā)呢?當(dāng)然我們確實(shí)是可以根據(jù)領(lǐng)域驅(qū)動(dòng)來(lái)開(kāi)發(fā),但是DDD被設(shè)計(jì)出來(lái)的完美初衷卻是設(shè)計(jì)。TDD強(qiáng)調(diào)的已經(jīng)是開(kāi)發(fā)了,要求開(kāi)發(fā)人員先寫(xiě)單元測(cè)試然后再通過(guò)不斷的迭代重構(gòu)讓單元測(cè)試通過(guò),以此來(lái)實(shí)現(xiàn)功能。這樣做的好處是強(qiáng)迫讓開(kāi)發(fā)人員清楚正確的理解需求,要知道這年頭沒(méi)有正確理解需求就開(kāi)始寫(xiě)代碼的程序員大有人在,并且我不認(rèn)為需求就是業(yè)務(wù),需求已經(jīng)是將本來(lái)的業(yè)務(wù)理解之后,轉(zhuǎn)化為了通過(guò)計(jì)算機(jī)可以實(shí)現(xiàn)的一些功能定義,通常是業(yè)務(wù)分析師或者項(xiàng)目經(jīng)理會(huì)去完成這個(gè)工作。而DDD中的D(領(lǐng)域)更像是本來(lái)的業(yè)務(wù),所以在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的時(shí)候,開(kāi)發(fā)人員或者架構(gòu)師直接與領(lǐng)域?qū)<遥ɑ蛘哒f(shuō)客戶(hù))進(jìn)行溝通來(lái)建模,這些業(yè)務(wù)模型也是以后開(kāi)發(fā)人員進(jìn)行設(shè)計(jì)和實(shí)現(xiàn)的依據(jù)。

領(lǐng)域模型被當(dāng)作開(kāi)發(fā)人員之間,開(kāi)發(fā)人員與領(lǐng)域?qū)<抑g溝通的橋梁,這樣可以閉免開(kāi)發(fā)人員用錯(cuò)誤的方式去實(shí)現(xiàn)功能。實(shí)際上很多優(yōu)秀的開(kāi)發(fā)人員,都會(huì)很自然的將現(xiàn)實(shí)世界中的問(wèn)題進(jìn)行抽象,然后用計(jì)算機(jī)的語(yǔ)言表示出來(lái),我們稱(chēng)之為面向?qū)ο蟆5怯捎谌鄙儆H臨其境的體驗(yàn),往往會(huì)離真實(shí)的業(yè)務(wù)模型有一些距離。

我們舉一個(gè)例子來(lái)說(shuō)明一下這個(gè)問(wèn)題,假如我們要開(kāi)發(fā)一個(gè)電子商務(wù)的網(wǎng)站,這個(gè)需求已經(jīng)非常清楚了,現(xiàn)在那么多的電子商務(wù)網(wǎng)站直接照抄一個(gè)就可以了。現(xiàn)在我們來(lái)做一個(gè)下單的功能,來(lái)看看怎么去實(shí)現(xiàn) 。

作為一個(gè)高級(jí)程序員,我們得用面向?qū)ο蟮姆绞饺ラ_(kāi)發(fā),先建類(lèi)。于是我們有了用戶(hù),訂單,訂單項(xiàng)的類(lèi),用戶(hù)創(chuàng)建訂單然后往訂單里面添加商品,添加訂單項(xiàng)的時(shí)候?yàn)榱朔奖悖覀冎恍枰獋魅氘a(chǎn)品ID和數(shù)量就可以了,于是Order類(lèi)有一個(gè)AddItem的方法。

作為一個(gè)高級(jí)程序員,一看這圖感覺(jué)很完美,有木有? 好,下面開(kāi)始實(shí)現(xiàn)AddItem方法。

Order里面是一個(gè)OrderItem的集合,而這個(gè)AddItem的方法接收的是productId,我去哪里搞個(gè)Product對(duì)象給你?我不可能在這個(gè)實(shí)體里面直接去查數(shù)據(jù)庫(kù)吧?本來(lái)是沖著這個(gè)技術(shù)點(diǎn)想咨詢(xún)一下大家,后來(lái)在小組里面討論了一下,我恍然大悟,上面的實(shí)體就是我從代碼的層面去思考想出來(lái)的,下單嘛,當(dāng)然是用戶(hù),訂單和訂單項(xiàng)嘍。可是只要去網(wǎng)上買(mǎi)過(guò)東西都知道,用戶(hù)是不會(huì)直接往訂單里面加?xùn)|西的,而是先把商品加入購(gòu)物車(chē),然后再通過(guò)“結(jié)算”一次性就根據(jù)購(gòu)物車(chē)生成了一張訂單,壓根沒(méi)有往訂單里面添加訂單項(xiàng)的行為。這才是真正的用戶(hù)行為(領(lǐng)域邏輯)所以后來(lái),我們的實(shí)體變成這樣了:

所以業(yè)務(wù)是這樣的:

  • 未注冊(cè)用戶(hù)也可以將商品添加到購(gòu)物車(chē)中,但是不能下訂單。
  • 并且購(gòu)物車(chē)中的商品不能保存起來(lái),用戶(hù)離開(kāi)這個(gè)網(wǎng)站(一般是關(guān)掉瀏覽器),購(gòu)物車(chē)中的商品就會(huì)消失。
  • 注冊(cè)用戶(hù)購(gòu)物車(chē)中的商品可以長(zhǎng)期永久保存,通過(guò)購(gòu)物車(chē)的“結(jié)算功能”,將購(gòu)物車(chē)中選中的商品轉(zhuǎn)化為訂單。
  • 所以購(gòu)物車(chē),應(yīng)該在用戶(hù)注冊(cè)的時(shí)候就應(yīng)該創(chuàng)建好,對(duì)應(yīng)我們上面的User實(shí)體中的CreatShoppingCart()方法。下面我們先來(lái)簡(jiǎn)單實(shí)現(xiàn)一下注冊(cè)的代碼。

//User領(lǐng)域?qū)嶓w代碼

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class User : BaseEntity  
  4.     {  
  5.         public string Name { get; set; }  
  6.         public string Email { get; set; }  
  7.         public string Password { get; set; }  
  8.         public Guid ShoppingCartId { get; set; }  
  9.         public virtual ShoppingCart ShoppingCart { get; set; }  
  10.         public virtual ICollection<Order> Orders { get; set; }  
  11.  
  12.         public void CreateShoppingCart()  
  13.         {  
  14.             ShoppingCart = new ShoppingCart  
  15.             {  
  16.                 Id = Guid.NewGuid(),  
  17.                 Customer = this,  
  18.                 CustomerId = Id,  
  19.             };  
  20.  
  21.             ShoppingCartId = ShoppingCart.Id;  
  22.         }  
  23.     }  

//領(lǐng)域?qū)?UserService.cs代碼

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class UserService  
  4.     {  
  5.         private IRepository<User> _userRepository;  
  6.  
  7.         public UserService(IRepository<User> userRepsoitory)  
  8.         {  
  9.             _userRepository = userRepsoitory;  
  10.         }  
  11.  
  12.         public virtual User Register(string email, string name, string password)  
  13.         {  
  14.             var user = new User  
  15.             {  
  16.                 Id = Guid.NewGuid(),  
  17.                 Email = email,  
  18.                 Name = name,  
  19.                 Password = password  
  20.             };  
  21.  
  22.             user.CreateShoppingCart();  
  23.             _userRepository.Insert(user);  
  24.             return user;  
  25.         }  
  26.     }  

//應(yīng)用層 UserService.cs代碼

  1. namespace RepositoryAndEf.Service  
  2. {  
  3.     public class UserService : IUserService  
  4.     {  
  5.         protected Domain.UserService DomainuUserService  
  6.         {  
  7.             get  
  8.             {  
  9.                 return EngineContext.Current.Resolve<Domain.UserService>();  
  10.             }  
  11.         }  
  12.  
  13.         public User Register(string email, string name, string password)  
  14.         {  
  15.             var user = DomainuUserService.Register(email, name, password);  
  16.             return user;  
  17.         }  
  18.     }  

上面是我們一次建模的過(guò)程,是一個(gè)將業(yè)務(wù)轉(zhuǎn)變成代碼,將現(xiàn)實(shí)世界抽象成軟件世界的過(guò)程。我們需要畫(huà)出模型不斷的與業(yè)務(wù)人員(領(lǐng)域?qū)<遥┤贤ǎ缓蟛粩嗟闹貥?gòu)去完善我們的模型,以至于這個(gè)模型能最準(zhǔn)確的反映真實(shí)的業(yè)務(wù)。這是在最開(kāi)始的設(shè)計(jì)階段,是需求溝通階段就需要做的工作,并且會(huì)一直貫穿我們后面的開(kāi)發(fā)甚至維護(hù)階段,沒(méi)有人可以一開(kāi)始就把領(lǐng)域模型建的100%準(zhǔn)確,需求是復(fù)雜的,并且需求還是隨時(shí)變化的,所以模型也會(huì)一直發(fā)生改變。它將作為開(kāi)發(fā)人員與業(yè)務(wù)人員、測(cè)試人員以及開(kāi)發(fā)人員自己之間溝通的橋梁。而DDD與其它方法論的區(qū)別之處就在于,它還提供了一整套的體系來(lái)保證后續(xù)對(duì)領(lǐng)域模型的重構(gòu)不會(huì)讓系統(tǒng)變得四分五裂,比如架構(gòu)分層,倉(cāng)儲(chǔ),依懶注入等等,我們后面再慢慢探討。

在DDD中,領(lǐng)域模型分為三種:

  1. 實(shí)體
  2. 值對(duì)象
  3. 領(lǐng)域服務(wù)

區(qū)分實(shí)體、值對(duì)象和領(lǐng)域服務(wù)

我們不打算去解釋以上的概念,我相信只要你搜索一下就可以得到很全面準(zhǔn)確的答案。但是重要的是我們一定要理解3者之間的區(qū)別,什么時(shí)候是實(shí)體,什么時(shí)候是值對(duì)象,又是什么時(shí)候我們?cè)撚妙I(lǐng)域服務(wù)呢?我想這是剛接觸DDD的人都難免會(huì)有些糾結(jié)的地方吧,在這里就強(qiáng)調(diào)一下。

實(shí)體相對(duì)于值對(duì)象而言擁有“標(biāo)識(shí)”的概念,標(biāo)識(shí)可以讓我們持續(xù)性的跟蹤實(shí)體。標(biāo)識(shí)和數(shù)據(jù)庫(kù)里面的“主鍵”是不一樣的概念,主鍵是技術(shù)上的概念,但是標(biāo)識(shí)是業(yè)務(wù)上的概念。

在我們上面的例子中用戶(hù)ID是標(biāo)識(shí),我們用它來(lái)持續(xù)性的跟蹤我們的用戶(hù)。訂單ID是標(biāo)識(shí),我們用它來(lái)持續(xù)性的跟蹤訂單,同時(shí)我們的用戶(hù)和訂單都是有著不同的狀態(tài)。但是對(duì)于用戶(hù)的地址來(lái)說(shuō),我們用什么來(lái)做標(biāo)識(shí)呢?在電子商務(wù)網(wǎng)站這樣的業(yè)務(wù)里面,我們不需要去持續(xù)的跟蹤這個(gè)地址信息,它在我們的系統(tǒng)里面也不會(huì)有著像訂單從“創(chuàng)建”、“已付款”、“已發(fā)貨”、“已收貨”等這樣的狀態(tài),所以地址信息的我們系統(tǒng)中就是一個(gè)值對(duì)象。

但是我們?nèi)绻麚Q了一個(gè)系統(tǒng),比如說(shuō)死慢的長(zhǎng)城寬帶,他們把地址作為跟蹤對(duì)象。同一個(gè)地址,誰(shuí)都可以去注冊(cè),但是同一個(gè)時(shí)間只允許一個(gè)人去注冊(cè),那么這個(gè)地址對(duì)于長(zhǎng)城寬帶來(lái)說(shuō)就去要去持續(xù)性的跟蹤,有“開(kāi)戶(hù)”,“銷(xiāo)戶(hù)”的狀態(tài)。那么地址信息對(duì)于長(zhǎng)城寬帶來(lái)說(shuō)就是一個(gè)實(shí)體。

解決完實(shí)體和值對(duì)象,領(lǐng)域服務(wù)就好說(shuō)了,一些重要的領(lǐng)域操作,既不屬于實(shí)體也不屬于值對(duì)象,那就可以把它放到服務(wù)中了。比如說(shuō)我們上面的領(lǐng)域服務(wù)UserService里面的注冊(cè)操作,注冊(cè)這個(gè)操作可以說(shuō)就是將這個(gè)用戶(hù)保存到我們的系統(tǒng)中。在注冊(cè)之間,這個(gè)用戶(hù)是不存在的,我們又怎么能把注冊(cè)這個(gè)操作放到User實(shí)體中去呢?所以把它放到領(lǐng)域服務(wù)中成了我們最好的選擇。

即使是這樣,哪些操作應(yīng)該放到領(lǐng)域服務(wù)中對(duì)于很多初學(xué)者來(lái)說(shuō)還是一件比較難選擇的問(wèn)題。也許只有慢慢的對(duì)業(yè)務(wù)越來(lái)越了解,對(duì)DDD應(yīng)用的越來(lái)越熟,我們就會(huì)少一點(diǎn)糾結(jié)。

#p#

厘清業(yè)務(wù)主次-聚合與聚合根

在上面的模型中,我們有很多關(guān)系的存在:用戶(hù)-購(gòu)物車(chē)(1對(duì)1),用戶(hù)-訂單-訂單項(xiàng)-產(chǎn)品(1對(duì)多,1對(duì)1),購(gòu)物車(chē)-購(gòu)物車(chē)項(xiàng)-產(chǎn)品等。在DDD中,我們把這樣多個(gè)模型用關(guān)聯(lián)串起來(lái)組成一個(gè)聚合(aggregation)

在我們的模型中,購(gòu)物車(chē)-購(gòu)物車(chē)項(xiàng)是一個(gè)聚合,訂單-訂單項(xiàng)是一個(gè)聚合我們通常需要保護(hù)這些聚合的一致性,比如說(shuō)我們把一個(gè)訂單刪掉了,那么這個(gè)訂單的訂單項(xiàng)也需要一起刪除,否則他們存在也沒(méi)有任何的意義。以前我們還會(huì)用到觸發(fā)器,但是大家都知道這個(gè)東西維護(hù)起來(lái)比較麻煩,寫(xiě)起來(lái)也不方便等,所以后來(lái)大家都是在代碼中來(lái)控制。但是一直沒(méi)有一個(gè)好的約束說(shuō)我們?nèi)绾稳ジ玫目刂七@些一致性,代碼一直都很散亂,直到DDD,我們有了聚合和聚合根的概念,“我們通過(guò)為每一個(gè)聚合選擇一個(gè)根,并通過(guò)根來(lái)控制所有對(duì)邊界內(nèi)的對(duì)象的訪問(wèn)。外部對(duì)象只能持有根的引用;由于根控制了訪問(wèn),因此我們無(wú)法繞過(guò)它去修改內(nèi)部元素。我們后面還會(huì)說(shuō)到只能為根來(lái)建立Repository,這也是為了確保我們這里面講的數(shù)據(jù)的一致性。

在我們上面的聚合中,只能通過(guò)購(gòu)物車(chē)實(shí)體來(lái)操作購(gòu)物車(chē)項(xiàng),而不能你自己寫(xiě)一個(gè)保存的方法直接就把購(gòu)物車(chē)項(xiàng)給保存到數(shù)據(jù)庫(kù)中去了。這就是聚合和聚合根起到的作用。我們來(lái)看一下我們購(gòu)物車(chē)實(shí)體的代碼:

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class ShoppingCart : BaseEntity  
  4.     {  
  5.         public ShoppingCart()  
  6.         {  
  7.             Items = new List<ShoppingCartItem>();  
  8.         }  
  9.  
  10.         #region Properties  
  11.  
  12.         public Guid CustomerId { get; set; }  
  13.         public virtual User Customer { get; set; }  
  14.         public virtual ICollection<ShoppingCartItem> Items { get; set; }  
  15.  
  16.         #endregion  
  17.  
  18.         #region Methods  
  19.         public void AddItem(Product product, int quantity)  
  20.         {  
  21.             // 如果該產(chǎn)品ID已經(jīng)存在于購(gòu)物車(chē)中,我們直接更改數(shù)量即可  
  22.             var repetitiveCartItem = Items.FirstOrDefault(  
  23.                 i => i.ProductId == product.Id);  
  24.  
  25.             if (repetitiveCartItem != null)  
  26.             {  
  27.                 repetitiveCartItem.Quantity += quantity;  
  28.                 return;  
  29.             }  
  30.  
  31.             Items.Add(new ShoppingCartItem  
  32.             {  
  33.                 Product = product,  
  34.                 ProductId = product.Id,  
  35.                 Quantity = quantity,  
  36.             });  
  37.         }  
  38.               
  39.         // 更改購(gòu)物車(chē)數(shù)量  
  40.         public void ChangeProductQuantity(Guid productId, int newQuantity)  
  41.         {  
  42.             var items = Items as ICollection<ShoppingCartItem>;  
  43.             var existingCartItem = items.FirstOrDefault(  
  44.                 i => i.ProductId == productId);  
  45.  
  46.             if (existingCartItem == null)  
  47.             {  
  48.                 throw new InvalidOperationException(  
  49.                     "Cannot find the product in shopping cart");  
  50.             }  
  51.             existingCartItem.Quantity = newQuantity;  
  52.         }  
  53.  
  54.         // 從購(gòu)物車(chē)中移除該產(chǎn)品  
  55.         public void RemoveItem(Guid productId)  
  56.         {  
  57.             var items = Items as ICollection<ShoppingCartItem>;  
  58.             var existingCartItem = items.FirstOrDefault(  
  59.                 i => i.ProductId == productId);  
  60.  
  61.             if (existingCartItem == null)  
  62.             {  
  63.                 throw new InvalidOperationException(  
  64.                     "Cannot find the product in shopping cart");  
  65.             }  
  66.  
  67.             items.Remove(existingCartItem);  
  68.         }  
  69.         #endregion  
  70.     }  

大家可以看到我們購(gòu)物車(chē)實(shí)體的邏輯很清晰,因?yàn)槲覀兒苊鞔_購(gòu)物車(chē)擁有哪些操作。當(dāng)然還有另一種做法即把這些操作都放到用戶(hù)實(shí)體中去,因?yàn)樽罱K其實(shí)是用戶(hù)做的這些操作。那我們的聚合就變成了用戶(hù)-購(gòu)物車(chē)-購(gòu)物車(chē)項(xiàng),這樣也沒(méi)有什么不可以,反而更符合真實(shí)的場(chǎng)景。但是會(huì)導(dǎo)致我們的聚合過(guò)龐大,也就是說(shuō)我必須要先有用戶(hù)實(shí)體才能進(jìn)行操作,用戶(hù)用戶(hù)可能會(huì)綁上很多的東西:購(gòu)物車(chē)、訂單、地址等等。在現(xiàn)在都是ajax來(lái)操作的大型網(wǎng)站中,我們需要在服務(wù)端把這個(gè)用戶(hù)請(qǐng)求加載出來(lái)再執(zhí)行添加購(gòu)物車(chē)的操作呢?還是可以直接加載購(gòu)物車(chē)實(shí)體來(lái)操作呢?這就是一個(gè)粒度的問(wèn)題,不同的問(wèn)題和場(chǎng)景,大家可以區(qū)別來(lái)對(duì)待。總之聚合是可以根據(jù)業(yè)務(wù)或者一些特定需求來(lái)做出調(diào)整的。比如說(shuō)購(gòu)物車(chē)-購(gòu)物車(chē)項(xiàng)-產(chǎn)品,這也是一個(gè)聚合,但是由于產(chǎn)品的特殊性,我們可以把產(chǎn)品也作為一個(gè)聚合根來(lái)單獨(dú)進(jìn)行訪問(wèn)。

我們來(lái)看一下應(yīng)用層ShoppingCartService的代碼:

  1. public class ShoppingCartService : IShoppingCartService  
  2. {  
  3.     private IRepository<ShoppingCart> _shoppingCartRepository;  
  4.     private IRepository<Product> _productRepository;  
  5.  
  6.     public ShoppingCartService(IRepository<ShoppingCart> shoppingCartRepository,  
  7.         IRepository<Product> productRepository)  
  8.     {  
  9.         _shoppingCartRepository = shoppingCartRepository;  
  10.         _productRepository = productRepository;  
  11.     }  
  12.  
  13.     public ShoppingCart AddToCart(Guid cartId, Guid productId, int quantity)  
  14.     {  
  15.         var cart = _shoppingCartRepository.GetById(cartId);  
  16.         var product = _productRepository.GetById(productId);  
  17.         cart.AddItem(product, quantity);  
  18.  
  19.         _shoppingCartRepository.Update(cart);  
  20.         return cart;  
  21.     }  
  22.  

此應(yīng)用層代碼一出,大家就會(huì)發(fā)現(xiàn),這代碼太簡(jiǎn)潔了,有木有?因?yàn)樗械倪壿嫛I(yè)務(wù)都被放到領(lǐng)域?qū)嶓w那里面去處理了。即使我們業(yè)務(wù)邏輯改變了,或者我們需要重構(gòu)了,它們都在領(lǐng)域?qū)嶓w那里面,改那里就好了。接下來(lái)的問(wèn)題是,如何確保安全,正確的一次又一次的對(duì)領(lǐng)域?qū)嶓w進(jìn)行重構(gòu)呢?畢竟它也是各種關(guān)聯(lián),各種依懶呀?您請(qǐng)接著往下看我們的單元測(cè)試環(huán)節(jié)。

#p#

獨(dú)立領(lǐng)域業(yè)務(wù)層 - 高內(nèi)聚,低耦合,可測(cè)試

講到這里,請(qǐng)?jiān)试S我從網(wǎng)上盜一張圖,當(dāng)然這張圖早就已經(jīng)是被引用過(guò)無(wú)數(shù)次了,它就是DDD中使用的分層結(jié)構(gòu)。

關(guān)于這個(gè)分層,每一層是干什么的,具體怎么玩,大家可以看一下dax的這一篇文章講解的很清楚。總之,我們的領(lǐng)域模型以及相關(guān)的類(lèi)比如工廠等會(huì)被獨(dú)立成為一層來(lái)與應(yīng)用層和基礎(chǔ)設(shè)計(jì)層交互。

 領(lǐng)域?qū)邮仟?dú)立的,首先它是應(yīng)用層的下層,所以肯定不會(huì)有對(duì)應(yīng)用層的依懶,但是領(lǐng)域有一些模型或者服務(wù)少不了是要與數(shù)據(jù)庫(kù)打交道的,比如說(shuō)我們?cè)谧?cè)用戶(hù)的時(shí)候需要去驗(yàn)證當(dāng)前的郵箱是不是已經(jīng)被占用了。而這一類(lèi)操作都是屬于基礎(chǔ)設(shè)施層做的事情,包含像一些數(shù)據(jù)庫(kù)操作,日志,緩存等等。那么我們?nèi)绾伪苊忸I(lǐng)域?qū)訉?duì)基礎(chǔ)設(shè)施層的依懶呢?感謝面向?qū)ο笤O(shè)計(jì) - 面向接口編程,只不過(guò)這里面的場(chǎng)景特別有代表性,它是一個(gè)非常常見(jiàn)的問(wèn)題,于是它成為了一個(gè)模式:倉(cāng)儲(chǔ)(Repository)。

  1. namespace RepositoryAndEf.Core.Data  
  2. {  
  3.     public partial interface IRepository<T> where T : BaseEntity  
  4.     {  
  5.         T GetById(object id);  
  6.  
  7.         IEnumerable<T> Get(  
  8.             Expression<Func<T, Boolean>> predicate);  
  9.  
  10.         bool Insert(T entity);  
  11.         bool Update(T entity);  
  12.         bool Delete(T entity);  
  13.     }  

一般情況下,我們會(huì)把倉(cāng)儲(chǔ)的接口放到領(lǐng)域?qū)樱蛘咭部梢栽俳ㄒ粋€(gè)Core層來(lái)作個(gè)項(xiàng)目最下面的那一層提供一些最公共的組件部分。關(guān)于倉(cāng)儲(chǔ)的代碼,大家在上面領(lǐng)域服務(wù)UserService中的注冊(cè)代碼中就已經(jīng)見(jiàn)到過(guò)了。可能需要注意的是,Repository用來(lái)將數(shù)據(jù)庫(kù)與其它的業(yè)務(wù)和技術(shù)分離,所以我們?cè)陬I(lǐng)域?qū)又惺褂盟€在應(yīng)用層中使用它。

Repository讓我們專(zhuān)注于模型,不用去考慮持久化的問(wèn)題。更為重要的一點(diǎn)是,因?yàn)樗墙涌冢晕覀兛梢院芊奖愕奶娲蛘吣M一個(gè)實(shí)現(xiàn)來(lái)對(duì)我們的領(lǐng)域模型進(jìn)行單元測(cè)試。下面是我們實(shí)現(xiàn)的MockRepository的代碼:

  1. public class MockRepository<T>: IRepository<T> where T : BaseEntity  
  2. {  
  3. private List<T> _list = new List<T>();  
  4.  
  5. public T GetById(Guid id)  
  6. {  
  7.     return _list.FirstOrDefault(e => e.Id == id);  
  8. }  
  9.  
  10. public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)  
  11. {  
  12.     return _list.Where(predicate.Compile());  
  13. }  
  14.  
  15. public bool Insert(T entity)  
  16. {  
  17.     if (GetById(entity.Id) != null)  
  18.     {  
  19.         throw new InvalidCastException("The id has already existed");  
  20.     }  
  21.  
  22.     _list.Add(entity);  
  23.     return true;  
  24. }  
  25.  
  26. public bool Update(T entity)  
  27. {  
  28.     var existingEntity = GetById(entity.Id);  
  29.     if (existingEntity == null)  
  30.     {  
  31.         throw new InvalidCastException("Cannot find the entity.");  
  32.     }  
  33.  
  34.     existingEntity = entity;  
  35.     return true;  
  36. }  
  37.  
  38. public bool Delete(T entity)  
  39. {  
  40.     var existingEntity = GetById(entity.Id);  
  41.     if (existingEntity == null)  
  42.     {  
  43.         throw new InvalidCastException("Cannot find the entity.");  
  44.     }  
  45.  
  46.     _list.Remove(entity);  
  47.     return true;  

下面我們給我們User領(lǐng)域?qū)嶓w的注冊(cè)方法加一個(gè)檢查Email是否存在的邏輯。

  1. public virtual User Register(string email, string name, string password)  
  2. {  
  3.     if (_userRepository.Get().Any(u => u.Email == email))  
  4.     {  
  5.         throw new ArgumentException("email has already existed");  
  6.     }  
  7.  
  8.     var user = new User  
  9.     {  
  10.         Id = Guid.NewGuid(),  
  11.         Email = email,  
  12.         Name = name,  
  13.         Password = password  
  14.     };  
  15.  
  16.     user.CreateShoppingCart();  
  17.     _userRepository.Insert(user);  
  18.     return user;  

在我們真實(shí)的Repository出來(lái)之前,不管我們是打算是EF,還是NHibernate,我們現(xiàn)在只要對(duì)這個(gè)Mock的Repository來(lái)編程或者進(jìn)行單元測(cè)試就可以了。

//UserService領(lǐng)域服務(wù)在單元測(cè)試

  1. public class UserServiceTests  
  2. {  
  3.     private IRepository<User> _userRepository = new MockRepository<User>();  
  4.  
  5.     [Fact]  
  6.     public void RegisterUser_ExpectedParameters_Success()  
  7.     {  
  8.         var userService = new UserService(_userRepository);  
  9.         var registeredUser = userService.Register(  
  10.             "hellojesseliu@outlook.com",   
  11.             "Jesse",   
  12.             "Jesse");  
  13.  
  14.         var userFromRepository = _userRepository.GetById(registeredUser.Id);  
  15.  
  16.         userFromRepository.Should().NotBe(null);  
  17.         userFromRepository.Email.Should().Be("hellojesseliu@outlook.com");  
  18.         userFromRepository.Name.Should().Be("Jesse");  
  19.         userFromRepository.Password.Should().Be("Jesse");  
  20.     }  
  21.  
  22.     [Fact]  
  23.     public void RegisterUser_ExistedEmail_ThrowException()  
  24.     {  
  25.         var userService = new UserService(_userRepository);  
  26.         var registeredUser = userService.Register(  
  27.             "hellojesseliu@outlook.com",   
  28.             "Jesse",   
  29.             "Jesse");  
  30.  
  31.         var userFromRepository = _userRepository.GetById(registeredUser.Id);  
  32.         userFromRepository.Should().NotBe(null);  
  33.  
  34.         Action action = () => userService.Register(  
  35.             "hellojesseliu@outlook.com",   
  36.             "Jesse_01",  
  37.             "Jesse");  
  38.         action.ShouldThrow<ArgumentException>();  
  39.     }  

我們用的XUnit.net作單元測(cè)試框架,同時(shí)用了Fluent Assertions。

結(jié)果很漂亮,有木有?有了單元測(cè)試來(lái)為我們的領(lǐng)域模型保駕護(hù)航,我們就可以安全的進(jìn)行重構(gòu)了。

 

干凈漂亮的代碼

經(jīng)常有人說(shuō)代碼是一件藝術(shù),碼農(nóng)都是藝術(shù)家。我很喜歡這句話(huà),如果你也認(rèn)同,那就請(qǐng)像對(duì)待藝術(shù)品一樣對(duì)待我們的代碼,精心的打磨它。并且你不一定要非常的有經(jīng)驗(yàn)才可以干這件事情;

如果你剛?cè)胄校侵辽俦WC一代碼可讀性好(好的命名,代碼邏輯清晰等);
再往上一點(diǎn),你要能夠更好的組織代碼(類(lèi),函數(shù));
等到你也成為專(zhuān)家了,那就開(kāi)始考慮一些重用性,可擴(kuò)展性,可維護(hù)性,可測(cè)試性的這些比較范的東西了;
而最后就上升到架構(gòu)層面,考慮系統(tǒng)各個(gè)組件之間通訊,分層,等等。最后你就成為碼神了。

DDD里面引入的一些思路包括分層、依懶注入、倉(cāng)儲(chǔ)等,可以給我們一些指導(dǎo),大家從上面的代碼也可以看出這些代碼組織的很好,邏輯也不會(huì)散亂的到處都是。當(dāng)然這個(gè)項(xiàng)目代碼量有限,說(shuō)服力是有限的,后面我們還會(huì)嘗試去加入應(yīng)用層的代碼。代碼已經(jīng)放到CodePlex上去了:http://repositoryandef.codeplex.com
歡迎大家Follow。注意代碼還沒(méi)有寫(xiě)完,只是一個(gè)初級(jí)版本,我們后面會(huì)慢慢完善。這個(gè)項(xiàng)目會(huì)使用EF來(lái)作業(yè)ORM框架,Autofac作依懶注入容器,用Xunit作單元測(cè)試框架的同時(shí)引入了Fluent Assertions。

小結(jié)

本文主要介紹了DDD的一些基礎(chǔ)概念:

  • 領(lǐng)域模型:領(lǐng)域?qū)嶓w、領(lǐng)域服務(wù)以及值對(duì)象;建模一定要從真實(shí)的領(lǐng)域業(yè)務(wù)出發(fā),多與領(lǐng)域?qū)<疫M(jìn)行溝通來(lái)完善模型。
  • 聚合與聚合根:它的主要作用是用來(lái)確保各種關(guān)系下的實(shí)體的數(shù)據(jù)一致性;但是確認(rèn)聚合根這個(gè)過(guò)程,實(shí)際上也是對(duì)業(yè)務(wù)的梳理過(guò)程。
  • 架構(gòu)分層: 每一層都職責(zé)清楚;依懶于接口來(lái)降低耦合。
  • 封裝和測(cè)試: 所有的業(yè)務(wù)都放到領(lǐng)域?qū)樱瑫r(shí)對(duì)領(lǐng)域?qū)舆M(jìn)行單元測(cè)試來(lái)確保最核心的邏輯不會(huì)遭到破壞。

個(gè)人感覺(jué)沒(méi)有必要太強(qiáng)調(diào)Repository的概念,從領(lǐng)域?qū)嶓w的生命周期(創(chuàng)建-持久化到數(shù)據(jù)庫(kù)-銷(xiāo)毀-從數(shù)據(jù)庫(kù)重建)你會(huì)發(fā)現(xiàn)其實(shí)這個(gè)過(guò)程很普遍,并不是只有DDD才有的。所以我認(rèn)為Repository主要是將數(shù)據(jù)訪問(wèn)功能給隔離開(kāi),避免領(lǐng)域?qū)嶓w對(duì)基礎(chǔ)設(shè)施層的依懶。那它和三層有什么區(qū)別? BLL 引用DAL不也是依懶于接口么?給我的感覺(jué)是,DDD的領(lǐng)域?qū)嶓w持久化這一塊就是三層里面的思路。這可能是在學(xué)習(xí)DDD初期的想法,因?yàn)檎鎸?shí)的大型項(xiàng)目中是不會(huì)直接把領(lǐng)域?qū)嶓w給持久化的,那個(gè)叫DTO,于是Repository<>里面放的就不是我們的領(lǐng)域?qū)嶓w了,而是將領(lǐng)域?qū)嶓w轉(zhuǎn)換成對(duì)應(yīng)的DTO。 

是否一定要使用DTO呢?領(lǐng)域?qū)嶓w和DTO互相轉(zhuǎn)換,最后到了表現(xiàn)層DTO還要和ViewModel轉(zhuǎn)換,會(huì)不會(huì)帶來(lái)復(fù)雜性和性能上的損失?Repository和EF還有Unit Of Work怎么來(lái)協(xié)調(diào)?抱怨寫(xiě)單元測(cè)試么?怎么樣讓寫(xiě)單元測(cè)試不變成只是走過(guò)場(chǎng)而已? 這些問(wèn)題留給我們后面再解決吧。

本文出自自:http://www.cnblogs.com/jesse2013/p/the-first-glance-of-ddd.html

責(zé)任編輯:林師授 來(lái)源: Jesse Liu的博客
相關(guān)推薦

2023-07-17 18:39:27

業(yè)務(wù)系統(tǒng)架構(gòu)

2020-09-27 14:24:58

if-else cod業(yè)務(wù)

2019-01-02 05:55:30

領(lǐng)域驅(qū)動(dòng)軟件復(fù)雜度

2022-04-07 17:30:31

Flutter攜程火車(chē)票渲染

2022-07-04 19:02:06

系統(tǒng)業(yè)務(wù)思考

2014-09-26 10:00:25

驅(qū)動(dòng)設(shè)計(jì)DDD領(lǐng)域

2014-04-10 09:49:38

System z關(guān)鍵業(yè)務(wù)

2024-12-20 19:38:01

ToB業(yè)務(wù)狀態(tài)轉(zhuǎn)換

2023-05-30 07:56:23

代碼軟件開(kāi)發(fā)

2021-09-08 09:22:23

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

2017-04-21 07:41:37

iOS自動(dòng)化測(cè)試容器

2019-08-14 08:52:40

業(yè)務(wù)代碼運(yùn)營(yíng)

2023-02-24 18:47:37

供應(yīng)鏈實(shí)時(shí)數(shù)倉(cāng)

2025-02-07 08:16:26

Java開(kāi)發(fā)者代碼

2018-12-11 14:18:11

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)ThoughtWork

2013-04-08 13:50:19

.NET系統(tǒng)架構(gòu)設(shè)計(jì)DDD

2023-08-29 07:53:17

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

2017-06-05 15:08:14

容量全鏈路流量

2013-04-11 09:52:17

.NET設(shè)計(jì)模式TDD

2015-03-31 16:25:35

Cocos
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲免费毛片 | 国产成人午夜电影网 | 欧美中文字幕 | 91中文字幕在线 | 日本h片在线观看 | 在线一区 | 国产视频一区二区三区四区五区 | 亚洲自拍偷拍欧美 | 免费视频中文字幕 | 在线播放国产一区二区三区 | 国产精品国产精品 | 三级黄色网址 | 精品国产一区二区三区久久 | 日日摸夜夜爽人人添av | 欧洲一级毛片 | 在线看av网址 | 久久久久香蕉视频 | 日韩www | 国产精品欧美一区二区三区不卡 | 免费激情网站 | 国产精品毛片av一区 | 曰韩一二三区 | 欧美日韩中文字幕在线 | 久久亚 | 亚洲a网| 久草网址 | 精品91久久 | 亚洲福利在线观看 | 成人小视频在线观看 | 福利网站在线观看 | 亚洲精品成人av | 国产精彩视频 | 毛片免费观看视频 | 999免费视频 | 日韩精品一区二区三区视频播放 | 久久成人免费 | 999热在线视频 | 天堂一区在线 | 精品一区在线 | 亚洲视频一区二区三区 | 欧美日韩视频在线 |