1. 程式人生 > >.NET領域驅動設計—看DDD是如何運用設計模式顛覆傳統架構

.NET領域驅動設計—看DDD是如何運用設計模式顛覆傳統架構

閱讀目錄:

  • 1.開篇介紹
  • 2.簡單瞭解緣由(本文的前期事宜)
  • 3.DomainModel擴充套件性(運用設計模式設計模型變化點)
    • 3.1.模型擴充套件性
    • 3.2.設計模式的使用(苦心專研的設計模式、設計思想可以隨意使用了)
    • 3.3.部分類的使用(封裝內部物件)
    • 3.4.高強度的OO設計(面向特定領域的高度抽象設計形成特定領域框架)
  • 4.DomainModel業務邏輯規則配置(將擴充套件點分離後使用適當的配置將規則IOC進去)
  • 5.DDD簡單總結(DDD是什麼?它是“戰術”) 

1】開篇介紹

這篇文章不會太長,但是絕對讓你對DDD有一個比較直觀的認可;

這篇文章所講到的內容雖然不多但是不太容易被領悟(因為多數人對DDD的理解還是存在很大誤區的;),當然也不是多麼神奇的東西,只不過是本人最近一直研究DDD的成果一個小小的心得與大家分享一下;

本文講的這些設計方式本身就存在著很大優勢,你會發現它與傳統三層架構最明顯的區別,這也是最有經典優勢的地方,最有價值的地方;

本來這篇文章是“[置頂].NET領域驅動設計—實踐(穿過迷霧走向光明)”一文的一部分但是由於時間關係,完整的示例並沒有跟文章同步釋出,說實話時間太緊,寫示例的目的是想全面的且細緻的闡述DDD的分析、設計各個環節的最佳實踐;原本想將文章的示例做好後在釋出,但是由於工作關係和一些私人原因可能有一段時間不更新部落格,又不想這篇文章拖的太久,所以我總結了兩點比較有價值的地方分享給大家,目的不是讓大家能會使用DDD來設計系統,而是能有一個突破點來衡量DDD到底比傳統三層好在哪裡,因為大部分人還沒有DDD的開發經驗所以能體會到應該沒有相關途徑;

網上很多的DDD文章有的還很不錯,但是本人也是從對DDD一竅不通再到目前對DDD有一個整體的瞭解,覺得最大的問題是讓能沒有接觸DDD的朋友能最貼切的體會到DDD到底哪裡好,而不是一上來就大片的理論還一些UML模型圖;其實完整的示例也只有這兩點最有價值了,因為它是DDD所強調的中心;

2】.簡單瞭解緣由(本文的前期事宜)

開始本文下面的章節之前先來了解一下我們將要做什麼設計,我假設您沒有時間閱讀“[置頂].NET領域驅動設計—實踐(穿過迷霧走向光明)”一文,比較文章也有點長了,所以這裡簡單介紹一下連續性的內容;

這篇文章我們將運用兩個常規的框架設計方法來對核心的業務進行細粒度的分解設計,在以往這點很難實現,所以我為什麼要說框架的設計思想,因為我們對設計模式的運用主要在框架、元件這些非業務需求性的基礎設施上;那麼這裡我們將用這些強大的武器來對最難對付的業務擴充套件性的設計;

本文全部的業務其實是一個簡單的學習考試系統的背景,我們下面將要運用強大的設計能力來對【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 }
View Code

【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 }
View Code

有一個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 }
View Code

有兩種級別,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】的業務邏輯進行高度的分析、設計、配置化;

3】DomainModel擴充套件性(運用設計模式設計模型變化點)

模型擴充套件性是一個一直被我們關注無數次提起的焦點,對它的把握始終未能實現;傳統分層架構將所有的業務邏輯灑滿每個層面,從UI層到資料庫都有多多少少的業務邏輯,而不是各負其責,管好自己分類的事情;UI層主要負責自己的樣子好看,不要這裡弄髒了那裡弄髒了;資料庫應該管好資料的儲存,資料的優化等等,兩者都沒有權利去管業務邏輯的權利;

這裡我們將要通過設計模式將對可能存在變化的業務邏輯抽象出來進行設計;

3.1】模型擴充套件性

在上面的介紹總我們大概瞭解了需求,下面我們要通過對【ParentMessage】的Priority屬性進行判斷,因為這兩種優先順序對於業務邏輯處理是不同的,但是可能會存在著相同的邏輯,這就完全符合我們的OOA、OOP的中心了,我們可以進行強大的抽象繼承來處理,這也是我們OO應該管理的範圍,UI\資料庫都不具備這樣的能力;

可以將DDD與UI、資料庫打個比方:

UI:我沒有什麼事情,分點業務給我處理吧;

資料庫:我很強大,所有的資料都在我的管理範圍之內,我想怎麼處理就怎麼處理,我天下第一;

DDD說:各位兄弟,要麼從一開始的時候就聽我的,要不然後面出了什麼事,我管不了你們了;——王清培;

設計模式很強大,能處理當前業務問題的有很多模式可以選擇,這裡我們使用常用的“策略模式”來解決不同Priority的邏輯;

3.2】設計模式的使用(苦心專研的設計模式、設計思想可以隨意使用了)

設計模式的強大不需要我再來廢話了,大家都懂;那麼這裡我們需要將邏輯的處理抽出來放入專門的邏輯處理類中去,這也符合向擴充套件開放向修改封閉原則;

將邏輯的處理獨立出去,跟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的繼承樹,還是比較簡單的;

3.3】部分類的使用(封裝內部物件)

在很多時候我們的設計需要藉助部分類來規劃物件的關係,以免汙染其他的實體;比如這裡的【Employee】需要在內部使用一個特定的型別,那麼最好是放在【Employee】內部使用,不要暴露在外面;這點在邏輯處理中進行設計比較合理;

圖12:

內部類再配合泛型一起用將發揮很大的設計奇效,這裡就不扯了;

3.4】高強度的OO設計(面向特定領域的高度抽象設計形成特定領域框架)

從上面的3.3】節中我們能體會到,對於特定領域的抽象其實是可行的,也就是說最終會形成強大的面向特定領域的框架、元件,但是這樣的框架是不通用的,也就是當前領域模型才能使用,這對於一般的專案而言確實成本很大,得不償失;然後對於需要長期維護的專案、產品、電子商務平臺值得投入,長期重構得出內聚性很強的領域框架;

圖13:

如果你一個框架做通用性的功能,只能做到泛泛而已,無法深入到業務內部;

圖14:

其實就是將精力集中在特定領域而已,逐漸重構出特定領域的框架;

4】DomainModel業務邏輯規則配置(將擴充套件點分離後使用適當的配置將規則IOC進來)

其實到了這裡,再說將業務邏輯配置化已經不是什麼大問題了,只需要將上面的IParentMessageOperation實現類通過IOC的方式配置好;但是這個配置的策略需要結合業務來判斷,可能存在多維度的判斷才能最終確定使用哪一個實現類,扯遠點如果後面配合C#4.0的超程式設計其實真的可以實現執行時配置邏輯策略了,但是目前來看不是很成熟;我們只有先將所有的業務邏輯實現好,然後根據業務需要進行後臺配置;

比如系統的後臺管理自動檢測是否是休息天,如果是休息天那麼對於【Employee】就沒有權利去執行【ParentMessage】的處理,是不是很簡單?當然很多好的設計可以慢慢的搬到系統中來,前提是“特定領域重構—特定領域框架設計”,這個度好把握好;

5】DDD簡單總結(DDD是什麼?它是“戰術”)

最近園子裡討論.NET技術值錢不值錢的文章很火,其實不管是.NET還是\JAVA都是工具,戰鬥的工具,必須具備一定的戰略設計才能讓他們彼此發揮具體的作用;可以把DDD比喻成孫子兵法,.NET只是打仗時的一個工具,JAVA也是如此,Python、ruby等等,關鍵是設計思想、戰略;所以我們長期培養的是設計能力,適當的熟悉某一種技術平臺,以不變應萬變;JAVA在牛逼,不懂企業架構一樣是垃圾,.NET再牛逼,不懂設計模式一樣玩不轉;

所有的技術框架都有其優缺點,我們只有先進行總體的設計、規劃,然後在適當的位置使用適當的技術,這個技術在這個方面比較擅長,那麼就把它安排在這個位置;.NET優勢在開發速度、UI上,那麼就用來進行前臺部分的開發;JAVA可能在大資料、分散式後端有優勢,那麼用來做伺服器開發,搜尋引擎;Ruby是動態語言,可以用來實現複雜的業務動態配置,集眾家之所長來完成一次大型的戰役,沒有誰離開誰轉不了,沒有誰比誰更重要,在一次戰鬥中連火頭軍都是不能少的,楊門女將中的楊排風誰看小看它,唯有軍師不能糊塗;謝謝;

作者:王清培

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。