閱讀目錄:html
這篇文章不會太長,可是絕對讓你對DDD有一個比較直觀的承認;數據庫
這篇文章所講到的內容雖然很少可是不太容易被領悟(由於多數人對DDD的理解仍是存在很大誤區的;),固然也不是多麼神奇的東西,只不過是本人最近一直研究DDD的成果一個小小的心得與你們分享一下;本文講的這些設計方式自己就存在着很大優點,你會發現它與傳統三層架構最明顯的區別,這也是最有經典優點的地方,最有價值的地方;編程
原本這篇文章是「[置頂].NET領域驅動設計—實踐(穿過迷霧走向光明)」一文的一部分可是因爲時間關係,完整的示例並無跟文章同步發佈,說實話時間太緊,寫示例的目的是想全面的且細緻的闡述DDD的分析、設計各個環節的最佳實踐;本來想將文章的示例作好後在發佈,可是因爲工做關係和一些私人緣由可能有一段時間不更新博客,又不想這篇文章拖的過久,因此我總結了兩點比較有價值的地方分享給你們,目的不是讓你們能會使用DDD來設計系統,而是能有一個突破點來衡量DDD到底比傳統三層好在哪裏,由於大部分人尚未DDD的開發經驗因此能體會到應該沒有相關途徑;後端
網上不少的DDD文章有的還很不錯,可是本人也是從對DDD一竅不通再到目前對DDD有一個總體的瞭解,以爲最大的問題是讓能沒有接觸DDD的朋友能最貼切的體會到DDD到底哪裏好,而不是一上來就大片的理論還一些UML模型圖;其實完整的示例也只有這兩點最有價值了,由於它是DDD所強調的中心;設計模式
開始本文下面的章節以前先來了解一下咱們將要作什麼設計,我假設您沒有時間閱讀「[置頂].NET領域驅動設計—實踐(穿過迷霧走向光明)」一文,比較文章也有點長了,因此這裏簡單介紹一下連續性的內容;ruby
這篇文章咱們將運用兩個常規的框架設計方法來對核心的業務進行細粒度的分解設計,在以往這點很難實現,因此我爲何要說框架的設計思想,由於咱們對設計模式的運用主要在框架、組件這些非業務需求性的基礎設施上;那麼這裏咱們將用這些強大的武器來對最難對付的業務擴展性的設計;服務器
本文所有的業務實際上是一個簡單的學習考試系統的背景,咱們下面將要運用強大的設計能力來對【Employee】聚合進行細粒度的設計、配置;以前的設計已經所有結束,數據持久化也設計完成,就剩下編碼階段;編碼的最大挑戰就在於前期的相關接口的設計,這裏是細粒度的接口設計,不是簡單的分分層;架構
圖1:框架
(查看大圖)分佈式
上圖中我用紅圈標記出咱們下面要擴展的【Employee】聚合,在將模型落實到代碼後咱們將要經過規約模式來將【Employee】的驗證對象化,而後經過設計模式的策略模式將規則策略化,再經過Configuraion Manager來管理全部的業務規則的配置,這個時候IOC就派上用場了,一切都很順手;傳統三層你是沒法作到的;
請看下面【Employee】實體類代碼:
1 /*============================================================================== 2 * Author:深度訓練 3 * Create time: 2013-07-08 4 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ 5 * Author Description:特定領域軟件工程實踐; 6 * ==============================================================================*/ 7 8 namespace Domain.DomainModel.ExaminationModule.Aggregates.EmployeeAgg 9 { 10 using System; 11 using System.Collections.Generic; 12 using Domain.DomainModel.ApproveModule.Aggregates.ParentMesAgg; 13 using Domain.DomainModel.ExaminationModule.Aggregates.FieldExaminationAgg; 14 using Domain.Seedwork; 15 16 public partial class Employee : EntityObject 17 { 18 public Employee() 19 { 20 this.ParentMessage = new HashSet<ParentMessage>(); 21 this.TeacherCheckGroup = new HashSet<TeacherCheckGroup>(); 22 } 23 24 public string EID { get; set; } 25 public string EName { get; set; } 26 public Nullable<Employee_Marry> IsMarry { get; set; } 27 public Nullable<int> Age { get; set; } 28 public string UserName { get; set; } 29 public string PassWord { get; set; } 30 public Nullable<Employee_Switch> SWitch { get; set; } 31 public Nullable<Employee_Role> EmpRole { get; set; } 32 public Nullable<Employee_Sex> Sex { get; set; } 33 34 public virtual ICollection<ParentMessage> ParentMessage { get; set; } 35 public virtual ICollection<TeacherCheckGroup> TeacherCheckGroup { get; set; } 36 37 public void ReSwitch() 38 { 39 if (this.SWitch.Value == Employee_Switch.IsFalse) 40 this.SWitch = Employee_Switch.IsTure; 41 else 42 this.SWitch = Employee_Switch.IsFalse; 43 } 44 45 public void Reinitial() 46 { 47 PassWord = "000000"; 48 } 49 } 50 }
【Employee】聚合跟通常的聚合沒多大區別,比較簡單的結構,爲了看起來完整一點,我加入了兩個初始化的行爲;ReSwitch是用來啓用、關閉當前帳戶;
Reinitial是初始化當前【Employee】的初始默認密碼,徹底是演示而用;
那麼咱們下面要作什麼呢?在以【Employee】爲聚合根裏面咱們聚合了【ParentMessage】家長留言、【TeacherCheckGroup】站考,兩個集合,其實這是用來作導航屬性的;實體框架須要這些信息作實體導航使用,在設計的時候你須要權衡你須要多少這樣的關聯;
如今通過咱們對需求的深刻分析以後可能會存在這樣的變更狀況:
【Parent家長】向【Employee教師】【留言】後,教師須要對留言內容作出反饋,好比要【及時的回覆】,對於不一樣的【留言級別】須要給出不一樣的處理;
這個需求很簡單,可是它裏面透露出來的是什麼?設計的擴展性,這個擴展性在哪裏?對於不一樣的【留言級別】須要給出不一樣的【處理】,很顯然是一個可能隨時會變化的點;
【Employee_Priority】代碼:
1 /*============================================================================== 2 * Author:深度訓練 3 * Create time: 2013-07-08 4 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ 5 * Author Description:特定領域軟件工程實踐; 6 * ==============================================================================*/ 7 8 namespace Domain.DomainModel.ApproveModule.Aggregates.ParentMesAgg 9 { 10 using Domain.DomainModel.ExaminationModule.Aggregates.EmployeeAgg; 11 using System; 12 13 public partial class ParentMessage 14 { 15 public string PMID { get; set; } 16 public string PID { get; set; } 17 public string EID { get; set; } 18 public string Content { get; set; } 19 public Nullable<Message_Priority> Priority { get; set; } 20 public Nullable<System.DateTime> Datetime { get; set; } 21 22 public virtual Employee Employee { get; set; } 23 public virtual Parent Parent { get; set; } 24 } 25 }
有一個Priority屬性,是標記該留言的緊急狀況,看代碼:
1 /*============================================================================== 2 * Author:深度訓練 3 * Create time: 2013-07-08 4 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ 5 * Author Description:特定領域軟件工程實踐; 6 * ==============================================================================*/ 7 8 namespace Domain.DomainModel.ExaminationModule.Aggregates.EmployeeAgg 9 { 10 using System; 11 12 public enum Message_Priority : int 13 { 14 Normal = 1, 15 Pressing = 2 16 } 17 }
有兩種級別,Normal表示普通的,Pressing表示緊急的,對於緊急的確定是須要先處理的,並且處理的邏輯或多或少有點不一樣;在DDD中全部的業務邏輯都要在DomainModel Layer 中處理,這是原則;全部的邏輯都不是直接使用,好比在登陸的時候咱們一般是驗證用戶名密碼是否真確,可是一般還會有不少其餘的條件,好比說當前用戶是不是高級會員、是否欠費等等,這些都是在聚合規約工廠中統一獲取的,這就便於咱們將變化的點抽到專門的地方進行設計;
邏輯判斷的地方原則是不直接寫IF\ELSE,邏輯處理地方原則是不直接寫實現代碼,經過接口實現策略類;
圖2:
咱們在【Employee】中加入了一個對【ParentMessage】實體的處理;因爲咱們的DomainModel一般不是直接持久化在MemberCache中的,因此對於有UI交互的操做都沒法很好的進行實體直接使用,若是是自動化的操做這裏的方法就不須要任何參數,每次都須要將留言的ID帶過來,而後咱們再進行內部的查找;固然這裏還能夠在Application Layer 就把【ParentMessage】實例拿到穿進來也能夠;
其實這個時候已經開始將進行細粒度的設計了,咱們看一下DomainModel結構;
圖3:
若是是數據庫驅動,咱們是沒法提取出【Employee】的相關對象的,一些狀態也只是數字表示而已缺少OO思想,也就談不上面向對象的設計了;這裏最讓人欣喜諾狂的是咱們已經徹底能夠將【Employee】相關的邏輯進行細粒度的擴展性設計了,這裏咱們將把全部跟【Employee】相關的全部業務邏輯都放入專門EmployeeLogic目錄中;這樣的好處真的不少,跟咱們最相關的就是項目的任務分配,這樣設計完成後就徹底能夠將某些邏輯抽象出來接口分配給某人去實現;
這一節主要就是介紹一下相關的背景,下面咱們就要將對【Employee】處理【ParentMesssage】的業務邏輯進行高度的分析、設計、配置化;
模型擴展性是一個一直被咱們關注無數次提起的焦點,對它的把握始終未能實現;傳統分層架構將全部的業務邏輯灑滿每一個層面,從UI層到數據庫都有多多少少的業務邏輯,而不是各負其責,管好本身分類的事情;UI層主要負責本身的樣子好看,不要這裏弄髒了那裏弄髒了;數據庫應該管好數據的存儲,數據的優化等等,二者都沒有權利去管業務邏輯的權利;
這裏咱們將要經過設計模式將對可能存在變化的業務邏輯抽象出來進行設計;
在上面的介紹總咱們大概瞭解了需求,下面咱們要經過對【ParentMessage】的Priority屬性進行判斷,由於這兩種優先級對於業務邏輯處理是不一樣的,可是可能會存在着相同的邏輯,這就徹底符合咱們的OOA、OOP的中心了,咱們能夠進行強大的抽象、繼承來處理,這也是咱們OO應該管理的範圍,UI\數據庫都不具有這樣的能力;
能夠將DDD與UI、數據庫打個比方:
UI:我沒有什麼事情,分點業務給我處理吧;
數據庫:我很強大,全部的數據都在個人管理範圍以內,我想怎麼處理就怎麼處理,我天下第一;
DDD說:各位兄弟,要麼從一開始的時候就聽個人,要否則後面出了什麼事,我管不了大家了;——王清培;
設計模式很強大,能處理當前業務問題的有不少模式能夠選擇,這裏咱們使用經常使用的「策略模式」來解決不一樣Priority的邏輯;
設計模式的強大不須要我再來廢話了,你們都懂;那麼這裏咱們須要將邏輯的處理抽出來放入專門的邏輯處理類中去,這也符合向擴展開放向修改封閉原則;
將邏輯的處理獨立出去,跟DomainModel之間存在着一個帶有陰影的重貼關係,雖然邏輯處理類相對獨立當時它畢竟仍是處於領域類的東西;將業務邏輯徹底的封閉在領域層中,可是在這個層中不是鬍子眉毛一把抓,仍是須要就具體的業務進行細粒度的分析、設計,對架構師來講是一個不小的挑戰,由於大部分的架構師比較關注純技術的東西,對業務的分析設計缺少經驗和興趣;
咱們來看一下對Priority的處理簡單設計:
圖4:
最理想的設計就是面向接口,【Employee】實體不會依賴哪個具體的實現類;
圖5:
咱們對Priority的處理邏輯抽象出來了相關的策略接口IParentMessageOperation,該接口有兩個接口分別用來處理不一樣優先級的具體業務邏輯;ParentMessageOperationNormal是處理Priority爲Normal的邏輯,ParentMessageOperationPressing是處理Priority爲Pressing的邏輯,固然爲了後面考慮我又加了一個abstract
calss ParentMessageOperationBasic 作一些相同邏輯的抽象;
一個簡單的Priority的邏輯均可以這樣去設計,我想再複雜的業務只要業務分析好,這裏的設計是不會有問題;到目前爲止我都在爲DDD的強大敢到震驚,我不相信你沒有看出來它能把多麼複雜的問題簡單化,以往是絕對不可能完成這樣的設計的,至少我歷來沒看見過也沒聽過誰能在傳統三層架構下把複雜的業務系統設計的很靈活,並且又不會污染UI、數據庫;
有策略接口那麼咱們還得把相應的實現類給綁上去,這裏有兩種方式,第一種使用簡單接口直接判斷而後建立策略實現,第二種是使用IOC的方式動態的注入進來,固然這裏已經到了咱們你們都比較擅長的範圍了,每一個人的設計思想不一樣就很少廢話了;
圖6:
看着這樣的結構,我沒有理解再說DDD不優雅;到了這裏已經很清晰了,咱們使用IParentMessageOperationFactory建立IParentMessageOperation,具體的邏輯封裝在以IParentMessageOperation接口爲主的實現類中;
圖7:
咱們經過IParentMessageOperationFactory建立IParentMessageOperation實現,是否是很清爽;
圖8:
這裏的代碼幾乎是不會隨着業務的變化而變化,要變化的是在邏輯處理裏面;
圖9:
接口的處理邏輯方法,很簡單約定一個【ParentMessage】、【Employee】兩個實體,這裏須要注意平衡實體之間的關聯性;
圖10:
經過基類能夠抽象點公共的邏輯,這裏是爲了演示而用;其實到了這一步你們都知道怎麼來進行設計了,關鍵是要分析好業務,而後得出深層領域模型,在此基礎上進行設計纔是靠譜的,不要爲了設計而設計,不要陷入技術的困境;
圖11:
該圖是咱們對priority相關邏輯的設計,頂層是兩個接口,右邊是一個Factory實現,左邊是Operation的繼承樹,仍是比較簡單的;
在不少時候咱們的設計須要藉助部分類來規劃對象的關係,以避免污染其餘的實體;好比這裏的【Employee】須要在內部使用一個特定的類型,那麼最好是放在【Employee】內部使用,不要暴露在外面;這點在邏輯處理中進行設計比較合理;
圖12:
內部類再配合泛型一塊兒用將發揮很大的設計奇效,這裏就不扯了;
從上面的3.3】節中咱們能體會到,對於特定領域的抽象實際上是可行的,也就是說最終會造成強大的面向特定領域的框架、組件,可是這樣的框架是不通用的,也就是當前領域模型才能使用,這對於通常的項目而言確實成本很大,得不償失;而後對於須要長期維護的項目、產品、電子商務平臺值得投入,長期重構得出內聚性很強的領域框架;
圖13:
若是你一個框架作通用性的功能,只能作到泛泛而已,沒法深刻到業務內部;
圖14:
其實就是將精力集中在特定領域而已,逐漸重構出特定領域的框架;
其實到了這裏,再說將業務邏輯配置化已經不是什麼大問題了,只須要將上面的IParentMessageOperation實現類經過IOC的方式配置好;可是這個配置的策略須要結合業務來判斷,可能存在多維度的判斷才能最終肯定使用哪個實現類,扯遠點若是後面配合C#4.0的元編程其實真的能夠實現運行時配置邏輯策略了,可是目前來看不是很成熟;咱們只有先將全部的業務邏輯實現好,而後根據業務須要進行後臺配置;
好比系統的後臺管理自動檢測是不是休息天,若是是休息天那麼對於【Employee】就沒有權利去執行【ParentMessage】的處理,是否是很簡單?固然不少好的設計能夠慢慢的搬到系統中來,前提是「特定領域重構—特定領域框架設計」,這個度好把握好;
最近園子裏討論.NET技術值錢不值錢的文章很火,其實無論是.NET仍是\JAVA都是工具,戰鬥的工具,必須具有必定的戰略設計才能讓他們彼此發揮具體的做用;能夠把DDD比喻成孫子兵法,.NET只是打仗時的一個工具,JAVA也是如此,Python、ruby等等,關鍵是設計思想、戰略;因此咱們長期培養的是設計能力,適當的熟悉某一種技術平臺,以不變應萬變;JAVA在牛逼,不懂企業架構同樣是垃圾,.NET再牛逼,不懂設計模式同樣玩不轉;
全部的技術框架都有其優缺點,咱們只有先進行整體的設計、規劃,而後在適當的位置使用適當的技術,這個技術在這個方面比較擅長,那麼就把它安排在這個位置;.NET優點在開發速度、UI上,那麼就用來進行前臺部分的開發;JAVA可能在大數據、分佈式後端有優點,那麼用來作服務器開發,搜索引擎;Ruby是動態語言,能夠用來實現複雜的業務動態配置,集衆家之所長來完成一次大型的戰役,沒有誰離開誰轉不了,沒有誰比誰更重要,在一次戰鬥中連火頭軍都是不能少的,楊門女將中的楊排風誰看小看它,惟有軍師不能糊塗;謝謝;
示例DEMO代碼(領域模型):http://files.cnblogs.com/wangiqngpei557/Domain.DomainModel.zip
做者:王清培
出處:http://www.cnblogs.com/wangiqngpei557/
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。