.NET領域驅動設計—實踐(穿過迷霧走向光明)

王清培發表於2013-06-30

閱讀目錄

  • 開篇介紹
  • 1.1示例介紹 (OnlineExamination線上考試系統介紹)
  • 1.2分析、建模 (對真實業務進行分析、模型化)
    • 1.2.1 用例分析 (提取系統的所有功能需求)
  • 1.3系統設計、建模 (技術化業務模型)
    • 1.3.1 列舉型別的使用 (別讓列舉型別成為數值型物件)
    • 1.3.2 基礎資料、業務資料 (顯示實體和隱式過程)
    • 1.3.3 模型在資料庫中的主外來鍵關聯問題 (物件導向模型與關係模型的天然抗阻)
    • 1.3.4 角色、型別 (區分型別與物件導向概念)
    • 1.3.5 名詞、動詞、隱、顯、抽象、具體 模型建立技巧 (物件導向分析技巧)
    • 1.3.6 永遠都不要去假設你的模型 (28原則)
  • 1.4重構模型 (規則引擎、精簡模型、模型擴充套件性)
    • 1.4.1 規則引擎 (複雜業務系統的一個重要分支)
    • 1.4.2 精簡模型 (聚焦系統核心,以業務模型為主)
    • 1.4.3 模型擴充套件性 (執行模式、常規法來設計物件導向)
  • 1.5系統架構設計、DDD分層架構 
    • 1.5.1 傳統分層架構 (無法滿足大規模業務系統而逐漸被淘汰)
    • 1.5.2 DDD充血型架構 (較豐滿的業務模型)
  • 1.6資料儲存設計 
    • 1.6.1 模型與關係資料之間的平衡 (分析、設計、架構的重要體現)

開篇介紹

在開始這篇富有某種奇妙感覺的文章之旅時我們先短暫的討論一下關於軟體開發方法論的簡要:

縱觀軟體開發方法論,從瀑布模型、螺旋模型、RUP(統一軟體開發過程)、XP(極限程式設計)、Agile(敏捷開發)一路走來,他們的好他們的美,我想接觸過的人都會口口稱讚,都是大師們一身的經驗結晶最後沉澱為專業的技術方向、技術領域,帶領我們軟體開發者們永無止境的前進,目睹一場又一場的美景一桌又一桌盛宴。他們在不斷的開闢新的領域,稱為偉大的科學家一點都不為過。

但是為什麼這麼多方法論都沒有能在企業中大面積的普及和使用或者說未能取得理想的效果呢,難道說是我們都不會嗎?當然不是,我想我們程式設計師都是很聰明而且很富有創造性思維的人群,我們敢於改變現狀追求真理,但是時間過去了很多,我們似乎都沒有真正解決複雜軟體的設計問題,我們參考很多書籍,數不勝數,擴充套件類、模式類、模型類太多太多,但是問題的核心始終未能觸碰到,在黑暗中無數的摔倒都未能找到突破口。為什麼DDD(領域驅動設計)能被我們接受並且願意花時間花精力去學習去實踐它,因為它發現了複雜軟體設計問題的核心解決方法(Model Driven Develop 模型驅動開發),聚焦複雜系統的核心,並且有一套完整的框架、流程指導我們進行相關DDD的設計、開發工作 。

在DDD未出現在我們面前時,我們遇見覆雜且龐大的業務系統的時候,會手忙腳亂的亂折騰,會發現根本無法拿下這麼一個龐大的Monster,最後專案就算僥倖成功也只是依靠個人力量英雄主義般的在獨自一人戰鬥,加班、熬夜精神極度集中,燃燒生命最後就算能取得成功,但往往系統最後還是這裡出錯那裡出錯,甚至還會漏掉什麼功能沒做,這算是一個常態,不足為奇了。

現在我們有辦法改變這種局面,團隊是幹什麼的?團隊不是樣子好看,三個臭皮匠賽過諸葛亮,只有一起參與系統的分析、設計才能最大化的保證需求的穩定性,最起碼做到一起評審分析方案。別說我太理想化,難道這不是我們共同的期盼嗎?

總而言之使用DDD的朋友都能感受到它的不一樣,愛惜生命的朋友請開始和我一起DDD(領域驅動設計)之旅吧!

1.1】示例介紹OnlineExaminationSystem

經過前面兩篇文章的講解,我們算是對DDD有了一個初步的認識,對它的概念它所提倡的開發原則開發思想有了一個基本的瞭解。任何方法論都要能被技術化落實到程式碼上才行,要能真正的為我們解決問題,所以我們這裡使用DDD進行一個完整的系統分析、設計、開發來驗證它確實如所說的那麼好,接受一個新的東西往往需要一個時間過程,所以文章可能會有點長,基本上都是一些理論;

前面兩篇文章的地址:

1.NET領域驅動設計—初嘗(一:疑問、模式、原則、工具、過程、框架、實踐)

2.NET領域驅動設計—初嘗(二:疑問、模式、原則、工具、過程、框架、實踐)

初次接觸DDD的朋友可以先閱讀一下上面兩篇文章,算是有一個巨集觀的理解,DDD是如何顛覆傳統的設計方法的。

專案背景介紹:

為了保證實踐專案的全面性,這裡挑選了我一直比較關注的教育行業資訊化的一塊【線上考試系統】作為實踐的專案,使用了C/S、B/S混合型的系統結構,系統的業務範圍主要是一個面向學校的學生線上考試系統。學生通過客戶端(C/S)進行線上答卷,答完卷後再通過遠端服務進行答題資料提交。老師通過後臺(B/S)進行試卷的打分,最後得出所有學生的成績資料並且在成績公告欄中顯示排名。(當然由於時間關係,示例程式碼可能推遲一點時間釋出;)

擴充套件:

教育類系統都存在一個問題就是【老師】、【學生】、【家長】三者之間是沒有任何資訊化聯絡;對於像考試類的管理都沒有任何方式告知家長學生的成績情況,包括最近的成績趨勢,還有就是學生的整體對比度等等。21世紀什麼最重要?人才;現在的家長都迫切的想知道每一天學生在學校的情況。所以對於這樣的需要很有價值去分析,去實踐起來,當然前提是教育需要進行改革才行。

1.1圖

(注:檢視大圖)

這是一個形象的需求思維導圖,很形象的描述了我們系統的大概功能,最重要的是能表達真實的業務場景,這也是模型驅動開發的首要思想。用專業的領域驅動設計思想來描述的話上圖那就是(解釋性模型)帶來的效果,如果我們用很抽象的程式專業圖形來表達業務需求很難說明問題,所以我們在前期與業務人員溝通大概需求時基本上給出我們人類天生就能理解的圖形化表示,可以視它為“解釋性模型”;當然由於時間關係並不會完成上面所有的需求;

在以【School DataCenter】為中心的【Student】學生【Teacher】教師【Parents】家長【Admin】管理員【CEO】最高執行人,各角色分別處理一個業務環節上的不同操作;

這只是簡單的專案介紹,目前大概就這些基本的需求,後面我們會進行詳細的系統分析。業務都具有發散性特點,看似簡單最後還是會出現很多需求問題,這也符合我們所需要的要求。從上圖中我們可以很簡單的就明白系統的基本功能,但是畢竟是一個簡單的需求草圖,只是我們腦子裡的一個雛形,我們需要把它落實到真正的專案中去,這個過程非常的不容易,唯一能正確穿過迷霧的方法是對照模型進行分析、設計。

1.2】系統分析、建模

上小節中我們基本瞭解到了系統主要有哪些功能而已,這一節我們將詳細的對系統進行業務分析,當然主要是為了突出領域建模的重要性,關鍵是它能為我們帶來什麼樣的好處。當我們逐漸按照DDD的方式來設計系統的時候,你會發現一切都很順利而且很OO,我們完全可以使用OOA\OOD的方式且沒有任何干擾的進行系統開發。

雖然說這是一個比較簡單的線上考試系統,但是如果要把它做好其實是蠻龐大的,會和很多其他的系統聯絡,所以這裡不會考慮太多的需求;

(這個專案本身的目的是為了演示DDD的設計、開發、架構,所以在需求上不會太難;)

1.2.1】用例分析

通過用例來分析系統中的基本功能呼叫,這部分的呼叫是侷限於外部的呼叫,畢竟是外部呼叫驅動內部呼叫;所有用例是捕獲外部呼叫的功能圖,具體的內部呼叫看具體的情況而定;

【學生用例】

學生用例主要就是進行系統考試,從【登入系統】【進入考場】然後【領取試卷】等待【答卷開始】通知,在限定的時間內進行【答題】;等時間到了之後系統自動進行【試卷提交】,當然自己也可以【提前交卷(條件是必須離考試結束時間10分鐘之內)】

2.1圖

【教師用例】

選擇分配給自己的試卷進行改卷(對於【選擇題】【判斷題】可以實現自動化的改卷;)。

教師的用例主要是【登入系統】,每個老師所要批改的試卷都是由【管理人員】進行分配的,所以這裡直接進入到自己所要批改的【試卷列表】即可,然後選擇某一個【試卷】進行批改,批改的【參照答案】應該是事先就已經準備好的,由N多位老師共同完成的一個標準的答案;

2.2圖

【管理者用例】

管理者只需要將所有的試卷分配給參與改試卷的老師們,這裡目前按照【考場進行分配】

一個考場需要進行N門科的考試,會有不同的老師站考,但是老師跟科目在這個時候沒有任何關係;等考完試之後,我們如何保證公平公正的對批改試卷老師的分配,由於考試的成績最後會對老師的晉升、年底獎金等等;所以這裡我先按照考場+科目進行分配,只有這樣才能保證最小的偏差;考場裡面的試卷都是經過隨機安排的學生提交的,所以不會存在問題;

2.3圖

【家長用例】

這裡我們不考慮太多家長的用例,基本的【簡訊通知】學生的考試成績和在班級、年級組的排名,如果複雜一點的可以有一個圖形的分析,家長可以進入考試系統進行分數的檢視或者與老師之間的線上交流;

2.4圖

【最高執行人】

這類人的功能我們這裡只包括一個學生成績的【圖形統計】

2.5圖

【系統分析技巧】

其實我們大部分程式設計師都不太願意與需求的提出人進行多溝通,認為他們提出的東西可能很不太理想;但是在DDD中,提倡頭腦風暴似的交流討論領域問題,過程是需要敏捷的、迭代的;從文章的一開始的專案介紹到用例分析,我想我們都會看到很多被我標記為“【***】”這樣格式的文字關鍵字,其實這個過程很重要很重要,這是我們與領域專家在溝通需求的時候需要積累、總結的領域語言,我們只有徹底的弄明白這些關鍵點才能為我們的分析打下一個初步的基礎,這樣才能迴圈漸進的進行迭代。

需求其實就是隱藏在我們交流的描述當中,要習慣性的把一些關鍵的字\詞先抽出來記錄下來,事後自己在慢慢的分析琢磨;你會發現很神奇的是這些關鍵部分恰恰是用例的重點也是下一步領域模型設計的依據;從專業的領域驅動設計角度講這些關鍵字都是領域通用語言的一部分,是我們進行交流的模型語言;我們在進行專案交流的時候會對一些口頭描述出來的需求產生二義性,但是我們如果使用領域模型進行交流的話就不會存在二義性,需求永遠都是等價的在我們之間傳遞;

1.3】系統設計、建模

按照上圖中的用例分析我們這裡需要對這些用例進行物件導向設計,也就是建立領域模型,得出領域模型之後我們的系統雛形就出來了;

看過前面兩篇文章的朋友就會對建立領域模型有點熟悉,建立領域模型有一個很好的設計思想就是(四色原型模式),它可以幫助我們很好的完善領域模型,找出核心領域模型之後就很容易進行模型邊界修飾逐漸的完善,還是那句話:需要敏捷、迭代的進行構造;沒有一次就能使用的領域模型,中間的過程是省不了的,需要不斷的重構、提煉才能使模型最後精簡而富有豐富的領域概念。

【詳細的四色原型模式後面會有專門的文章來深入的講解,這裡大家只需要知道型別原型就行了,有興趣的朋友可以自己百度相關文章】

【領域模型】

如果得出領域模型,需要我們對上面的用例進行細緻的分析並且逐漸的勾畫出邊框,再慢慢的修飾得出一個基本的模型圖,但是我們要知道一個有血有肉的模型是需要不斷的去呵護它才行的;我們需要不斷的重構,不斷的剔除一些障礙留最真實的模型,最後領域模型將是本次專案的一筆豐富的財富;

我們一個一個用例來過,首要的是學生的用例;

【學生用例模型】

首先是【學生】主體模型,這裡我們可以將【四色原型模式】的思想引進來了,你會發現你突然很會設計模型了(呵呵,開個玩笑!);對照四色原型我們知道【學生】應該是有型別之分的,那麼學生的型別的分類屬性是按照什麼來呢?比如是性別(男、女)、職務(班長、組長)、成績優差(優、良好、差)等,決定要使用什麼屬性作為我們的分類標準需要看系統的需求來定了,但是最起碼四色原型模式告訴你你缺少某種分類;那麼我們這裡只需要按照學生的性別進行一個基本的型別分類就行了;

3.1圖

有了一個基本的核心點之後我們後面的思路其實基本上就會好延伸下去,我們繼續分析;

看到了【Student】發起的第一個用例是登入系統,既然是登入系統那麼肯定是要使用者名稱、密碼的,並且一般性的約束是要控制是否啟用、禁用該學生,比如學生不在本所學校了,不可能將學生的資訊刪除的,它牽涉到很多其他的系統和業務資料;所以我們這裡又增加了三個基本的屬性,“使用者名稱”、“密碼”、“啟用禁用”標誌;

3.2圖

我們加上了三個基本屬性;好像還差點什麼?學生的基本資訊似乎沒有,那麼我們加上關於學生的基本屬性(學生的姓名、年齡、學號);

3.3圖

那麼學生的基本資訊算是沒問題了,我們繼續沿著用例分析;【進入考場】用例是學生選擇相應的考場然後進入,在真實的線下考試當中每個考生在考試的時候都會有一個考號,憑考號進入考場才對;但是我們這裡不能將【考號】直接放入到學生的基本資訊當中去,為什麼呢?因為一個學生在學校期間會經歷N場考試;所以我們需要獨立的模型來表示學生與考號之間的關係;

但是我們自己分析一下,考號的生成是有一定的規律的,它一般都是跟學生所在的考場資訊掛鉤的,比如:0320,應該是第3考場第20座位;我們在反推一下,其實考號跟學生在考試之前的某一個時間段他們之間是沒有任何聯絡的,也是說應該是先安排考場然後再根據考場中的座位再進行考號的生成,而且每個時間段的考試都是一個年級組的,要麼初一的所有學生考試,要麼初二的所有學生考試所以當考號生成後將隨機的對映到每個學生身上;當然也有另外情況就是每次考試時間週期內會有多個年級組進行考試,但是這個時候考場中的考生是如何設定的就要看具體的需求而定了,由於話題比較大所以這裡就不涉及了。這裡就當它是獨立年級組進行考試;

考場就是班級,在考試的時候所有的班級都將會變成考場的屬性,比如原本是“三五班”但是考試的時候就變成了03考場了,所以我們需要對班級進行建模,有了班級之後才能慢慢的設計考場的模型。

3.4圖

班級的型別存在多個屬性的分類,”Class_UseType”是表示班級的使用情況,比如班級在裝修的時候可能就是Close狀態,平時都是Open狀態;另外還存在著一個班級是被哪個年級使用的,比如是初一還是初二,或者是高一、高二等等;我們需要明白的是,型別是事前約定好的,型別是確定的模型,它與可維護基礎資料是不同的,後面會有專門的小結做詳細的講解;

這裡的Class_UseType模型是代表當前班級的使用情況,班級會存在“使用”、“停用”情況,比如某個班級在裝修或者其他的問題;這中分類完全可以用列舉型別進行表示,但是Class_GradeType班級所屬年級,代表當前班級是屬於哪個年級組的,這個屬性在後面肯定是會用到的,比如要將所有的初一班級作為本次考試的場地,這個時候就用到了;在Class中有一個PewNumber的屬性是表示當前班級的座位數,一般考試都是50%的座位是在考試中使用;

本打算把所有的用例模型的分析都寫出來的,但是這裡由於時間關係就不一步一步分析說明了,對於學生考試的這部分模型圖差不多是這樣子的:

3.5圖

麻雀雖小五臟俱全,儘管這裡截圖不是全部的(有全部原始碼供下載),但是能說明一個問題:利用領域驅動設計開發軟體確實對設計技術要求較高,圖中藍色的都是潛在的深層模型,而灰色的是我們最容易發現的表層模型。我們往常進行物件導向設計的時候都是很容易的發現一些表層的模型比如:人、車等一些實物,但是很難發現一些潛在的模型比如:一次婚紗拍攝場地,一次吃飯過程等等,這些潛在的模型都是核心業務模型,所以當我們使用DDD進行軟體開發的時候不知不覺的就會讓你對業務清晰度要求變高了,不會存在含含糊糊的業務需求;

1.3.1】列舉型別的使用

在一般的框架型專案中都會使用列舉來表達某些概念上的一個序列,列舉是約定的表達,列舉只限定在它的區間取值;比如我們都愛寫寫框架、元件被別人使用,大家最熟悉的莫過於ORM框架了,ORM框架裡面都會有本框架所能支援的資料庫種類列舉,使用列舉來約定只能使用這區間的值,沒別的選擇;

但是列舉我們也可以用在DDD上,在以往的業務性系統中很少能看見和業務相關的列舉,都是功能性的,比如寫Log、Email的等等;然後這裡我們需要把它做為在DDD中的核心物件模型來使用,比如使用者的登入型別、支付方式,前提是已經約定好的;

列舉型別與基礎資料會存著混淆,我到底使用列舉還是基礎資料結構,這裡很簡單的區分就是看你的基礎資料日後是否需要不斷的維護擴充套件;比如學校的班級日後肯定是需要不斷的修改或者新增新的班級,而學校日常考試範圍基本上都是鎖定在那幾門,中國的可是科目就那幾種完全可以直接定義列舉約定;在編碼階段很簡單的進行列舉出值,如:FieldExamination.Subject==Subject.English,將本場考試為English作為條件;通常列舉型別都是作為值型別出現在DDD中;

1.3.2】基礎資料、業務資料

【基礎資料】

基礎資料是系統在上線執行之前就已經維護進去的啟動資料,比如我們這裡的【班級】、【學生】、【教師】等等,用來支撐系統執行的必備資料,這些資料普遍存在一個特點那就是“實體”資料,那麼我們如何斷定是不是一個基礎資料,按照分析模式“四色原型模式”,中的“參與者、地點、物品(party, place, or thing ”原型我們可以把它想象成是可以進行任何獨立使用的獨立單位物件,叫做“基礎資料”

【業務資料】

業務資料很明顯的特點就是發生在某個時間段上的事情,比如我們的一次購物、一次郊遊、一次拍照等等,都是建立在基礎資料之上的,從現實角度去分析人物事情的發生都是需要人的介入,當人介入之後將發生對某物的操作,比如:強強在2013年6月1日參加了學校舉辦的六一兒童節活動,這裡的【強強】是基礎資料而發生的這一整個事件為業務資料,也是“四色原型模式”中的核心原型,業務資料牽動著基礎資料,關聯著彼此不相關的基礎資料;同樣我們使用“四色原型模式”中的“某個時間段的間隔(moment—interval)”原型我們就可以很明顯的找出什麼是業務資料資料;

這裡我順便扯一下“四色原型模式”並不是Martin Flower大師所寫的分析模式,他寫的應該歸類於“業務原型”,而這裡的“四色原型”是peter code的傑作,這裡就不多講了,畢竟我對四色原型也只是初步的瞭解;【對四色原型有興趣的朋友可以直接看《彩色UML建模》Peter Code 大師的書】

基礎資料與業務資料之間的關聯需要很小心,我們要衡量好之間的關係。當我們識別出基礎資料之後在首次進行建模的時候為了合理的表達領域模型的完整性會將其關聯的很緊密,在團隊中進行交流評審的時候都是很有幫助的,到了後面重構階段一定會將龐大的蜘蛛網合理的拆開形成精簡的聚合模型;

本節想強調的是正確的識別出領域中的“基礎資料”和“業務資料”,讓後將其合理的關聯來表達領域模型;

1.3.3】模型在資料庫中的主外來鍵關聯問題

當模型落實到程式碼上的時候我們就要考慮如何將模型在關係型資料庫中儲存的問題,當然你可以存放在任何地方,但是不同的資料儲存方式會對你的模型有一定程度的影響或者說會影響你建模的細節思路,這是需要平衡的;有人會說:“模型的建立應該完全不用去考慮到底如何持久化”,難道真的是這樣的嗎?我通過實踐證明問題恰恰相反,當你在建模的時候如果不懂的技術實現那麼就會產生像DDD書中所說的分析與設計之間的裂縫,分析人員分析出來的模型根本無法在真實的技術環境下實現;難道你還會說:”分析的時候完全不用去考慮到底如何實現“,這就是DDD所說的複雜軟體開發問題所在;

在目前情況下普遍認為分析人員大於程式設計師,他們占主導地位,想當然的去搜集業務然後交給你實現,當他讓你實現一個很彆扭的功能的時候其實你完全可以用自己的專業意見來改善的很平滑,但是由於工作職責的不同他們並不是很懂的技術實現的細節所以問題就在這裡;

【這不是我結論,在《領域驅動設計.軟體核心複雜性應對之道》一書中,Eric Evans 是這麼定義的,詳見書中第7章;問題確實如此;】

這裡我們只討論面向關係型資料庫的儲存方式;聚合是一類實體的集合,會有一個“帶頭”的實體也就是聚合根,我們對它的操作需要很小心,比如:當你插入一個聚合根時會把聚合根所涉及的一些附屬模型都插入,這個時候就是錯誤的;這個時候如果沒有很好的資料訪問元件來支援的話我們很難保證資料的一致性,後面我會專門寫一個系列針對Microsoft.EntityFramework的文章,因為目前.NET平臺穩定的實體框架就屬實體框架EF了;

按道理我們的聚合是一個帶有根的實體集,他們被邏輯劃分到一個業務範圍中,比如【FieldExamination】每場考試聚合,當我們查詢有關一場考試資訊的時候會關聯出它所附屬的一些其他資訊,這是查詢沒有問題,但是當我們進行刪除、更新、新增的時候問題沒有那麼簡單了,當然是可以避免了這裡跟大家分享一下需要注意的地方;

public class FieldExamination:EntityRoot

{

   public string FId{get;set;}

   public Datetime BeginTime{get;}

   public Datetime ProcessTime{get;}

   //獲取本場考試的試卷

   public  GetCurrentExBook(string stuId){//……}

   //本場考試的負責人

   public Employee  Principal{get;}

   public Subject  CurrentSubject{get;}

}

public class FieldExaminationRepostiroy:Repository<FieldExamination>

{

  public FieldExamination GetById(string id){//……}

}

上述FieldExamination實體沒有任何問題,恩 看起來是沒有問題;當我們對資料庫進行查詢的時候是沒問題的,會順利的得到FieldExamination實體確實很方便,這也是DDD的精神所在;但是當我們進行物件的插入的時候問題來了,通常我們對實體進行建立的時候是會通過一個專門的Factory來建立,這個物件是一個完整的,包含了基本的屬性資訊,比如這裡的FieldExamination實體建立的時候是肯定要知道它的負責人是誰並且本場考試的科目是什麼,我們會對相關的屬性進行賦值,那麼這個時候進行插入的時候就會將相關的屬性插入到屬性所要持久化的表中去,這裡也就是會將Principal屬性插入到Employee表中去,那麼就是錯誤的,同樣其他的屬性都是這種情況;

那麼到底如何解決,其實就是通過“標量屬性”來解決,這個時候實體會增加一些屬性所對應的“屬性欄位”如:

public class FieldExamination:EntityRoot

{

  public string FId{get;set;}

  public Datetime BeginTime{get;}

  public Datetime ProcessTime{get;}

  //獲取本場考試的試卷

  public GetCurrentExBook(string stuId){//……}

  //本場考試的負責人

  public int PrincipalId{get;}//查詢的時候這個屬性不需要關心

  public Employee Principal{get;}

  public int CurrentSubjectId{get;}//查詢的時候這個屬性不需要關心

  public Subject CurrentSubject{get;}

}

也就是說進行插入、更新的時候只需要使用“標量”屬性來更新插入即可,因為不需要涉及到對其他物件的操作;

1.3.4】角色、型別

模型的角色、型別,我想大家應該多少有點了解的,如:訂單有訂單的型別,考試有考試的型別,這也是“四色原型”中所講“Role”原型;不管是從什麼維度進行分類、分角色都是有必要的,當你缺少角色分類是應該提醒自己可能你漏掉了什麼,因為據以往經驗告訴我們“角色、分類“肯定是會用到的;

在我們前面對學生用例進行分析的時候很多地方都是需要角色、型別,使其看起來很合理;

1.3.5】名詞、動詞、隱、顯、抽象、具體  模型建立技巧

這裡給大家總結一下系統分析的一些基本的技巧,亂七八糟的理論這裡就不扯了,有興趣的朋友可以看專門的書籍,這裡是比較簡單能直接用的技巧;

【名詞】—>【顯】

當我們和業務人員進行業務溝通的時候我們會聽多很多【業務名詞】,首次談話業務名詞對我們來說可能比較難以理解尤其是複雜的業務領域,當然會隨著多次的溝通逐漸的理解並且得出比較合理的領域通用語言;在很多時候【名詞】法能很快的捕獲到最直接的【顯】層模型,比如在本示例中我們能很快並且很準確的將【名詞】中如:學生、教師、班級等等這些實體模型,這些實體資訊最容易被人理解和接受;當我們和業務專家進行溝通業務的時候不要光聽他們講要把他們講的每一個業務環節中的涉及到的業務名詞記下來然後自己再過一遍對不懂的一定不要假設什麼什麼,一定要虛心的向他們討教哪怕他們真的煩了那也沒辦法,因為這是工作;

【動詞】—>【隱】

同樣和業務專家進行溝通的時候會有很多【過程】、【動作】等等這些名詞出現,比如進行【一場考試】,那麼就會涉及到對動詞的抽象了,顯然【一次考試】是”四色原型“中的”moment—Intervel“,對於這樣捕獲下來的模型是具有很強的業務性的,這樣細心的分析慢慢的挖掘這些潛在的動詞模型,也有可能動詞模型會覆蓋動詞模型,比如一次考試可能已經包含老師站考記錄,一次考試會隱藏諸如這些潛在的模型;

具體的模型是我們一眼就能看穿的實體模型,是一些人、物,而我們很難發現是過程模型也就是複雜的業務流程模型,在某個業務環節下要涉及到很多業務模型,最重要的也就是【發生了什麼事情】,只有發生了事情才能將人、物關聯起來;

【具體=名詞、顯】—>【抽象=動詞、隱】

被我們意識能直接識別出來的通常都在我們的知識水平面上,只有具體的東西才能支撐抽象的事物;如果沒有人會有【訂單】嗎,如果沒有貨物會有【配送】嗎,如果沒有CUP、記憶體會有程式嗎;識別出顯示的模型當然是最直接的引導方式;

3.6圖

上圖是一隻抽象的魚(abstract fish),如果沒有具體的骨架(Concrate Frame)它是不會有形狀的,想要得到這隻魚必須得有骨架模型幫你支撐起來才行;同樣的道理,我們在分析系統的時候也一樣的,需要識別出具體的事物然後才能穩健的抽象出模型;(當然並不是絕對的,你也可以進行抽象優先,但是我想前提是你腦子裡已經有具體的事物了)

1.3.6】永遠都不要去假設你的模型

我們以前經常會犯一個錯誤,就是經常去假設系統能提供什麼功能,比如我們在分析一個系統的時候總是喜歡假設它應該具備什麼功能,那麼這些功能真的能替你畫龍點睛呢,還是在畫蛇添足;普遍現象是分析人員在進行分析的時候都沒有一定程度的搞懂需求的真正目的是什麼,每一個需求的背後是價值驅動的,一定要讓業務人員告知你一二,這樣可以方便你自主能動的去舉一反三,而不是他說“告訴你也不懂”你就不好意思的結束了,千萬不;如果他不告訴你清楚的需求你就無法畫龍點睛,無法進行知識消化也就談不上模型重構了;切忌不要假設我們應該做什麼功能,我們所有的功能需求都是業務人員需要的,或者是潛在需要的,這個潛在是他跟你講了需求背後的價值才能設計;

1.4】重構模型(規則引擎、精簡模型、模型擴充套件性)

這個過程是設計階段最重要的核心過程;我們一直都認為自己的設計能力不錯,就是一直沒找到合適的地方運用,至少在資料庫驅動的軟體開發中你是用不上什麼設計思想的,邏輯都在資料庫的儲存過程裡面;你的物件導向設計如何的了得不,你的面向介面程式設計運用的如何的出神入化,你的諸多好的模式都用不上;但是在DDD的(重構模型)階段你將可以大展身手,用專業術語來說這個階段是(設計模式)介入的階段,通過深入的挖掘業務潛在的變化點,通過模式將變化點抽象出來,將變化點隔離在系統的外部;

1.4.1】規則引擎

這裡隨便提一下(規則引擎)的相關概念;

我們都知道物件導向設計思想真的很神奇,是那些軟體大師、科學家研究出來的,在倡導進行DDD驅動的時候我們用物件導向的思想來抽象領域模型,用C#、JAVA之類的面嚮物件語言來實現等價的模型程式碼,但是在一個系統中物件只是一種模型;在DDD的分層架構中的Business Layer中我們放置的都是Domain相關的模型,在這一層也就是最核心的一層裡,我們用物件來表示所有的業務模型,就好比最小粒度的細胞一樣;但是這些模型沒有一個點將他們穿起來形成一個整體性,比如在業務系統中都存在著業務流程,那麼流程需要使用到的Domain如何被模型化,其實也就是工作流(Workflow),工作流模型用來抽象所有的業務流程,也就是我們DDD分層架構中的Application Layer中的元素,所有的呼叫進入到Application Layer 層之前都是有先後順序的,比如審批流程,要先提交審批的相關單據資訊,然後才能到達【審批 Public bool Auditing(Forminfo info){//審批邏輯}】的環節,這樣的流程需要相關的框架支援才行;我們有工作流引擎來支撐,但是DDD的核心元素模型業務規則(Business specification)還無法很好的在我們系統中實現;這本身就是靜態語言的一個問題,靜態語言的所有邏輯在編譯時就已經確定,不管你是直譯式還是中間式的編譯過程,本身語言的設計就是這種靜態思想;

我們大家對Js多多少少肯定是比較熟悉的,它本身就是一個動態的語言,我們且不問是否是指令碼語言;它能很好的解決在執行時動態的改變物件的所有屬性、行為;這一點在很大程度上便於我們對規則的設計,由於規則是動態變化的,所有動態語言是用來開發規則引擎的一個好的工具;當然並不是規則引擎就是這點東西;我們可以適當的研究一下Ruby、Python之類的動態面嚮物件語言,對規則的設計是很有好處的;從效能角度將JS肯定是不能在伺服器端使用的,可以使用Python編寫規則引擎,當然還有很多函式式語言都很不錯;在很大程度上改善了一門語言設計系統的限制;語言各有優缺點用在合適的位置都很好;微軟的.NET/F#的出現很有可能是為了解決類似問題的,函式式語言是天生的規則模型語言;

【規則引擎的位置】:

在Business Layer 中到處充滿了業務規則,規則引擎是獨立的系統元件,本身的位置應該處於Infrestructure Cross Layer中,但是它屬於架構框架會對業務層有衝擊性,如果設計不好的話甚至會嚴重汙染DomainModel,所以挑選一款合適的規則引擎元件或者自己開發都要很慎重;

4.1圖

(注:檢視大圖)

上圖中我們看到對於Student、Teacher、Parent三個角色的幾個用例活動,用例的活動行為有些是在模型內部的,而有些是在應用層處理的;但是都離不開當前的業務規則,按照上述描述我們的規則引擎是統一管理業務規則的地方,對於任何一個環節需要業務規則的都將通過在規則引擎中獲取並且直接執行;規則引擎是實時執行著的,對於大型實時線上系統必須要滿足這點,不可能將規則儲存在磁碟檔案上,比如關係型資料庫中、序列化的檔案中;應該將它儲存在記憶體中或者通過分散式快取技術放在可以很快且很方便獲取的地方;

這樣將規則獨立出來可以改變很多事情,甚至有可能顛覆你對業務系統業務邏輯的設計思路;只有這樣設計才能真正談得上是最大粒度的擴充套件性;我們進一步擴充套件,將Business Specification Engine Component 設計於在後臺管理中進行動態規則維護;

4.2圖

(注:檢視大圖)

在原圖中我們加入了SOA介面層,該介面層是通用的後臺維護介面;通常SOA被放在真實的使用者端所呼叫的外網,但是這裡的位置是出於公司內部網路,可以減少關於安全方面的設計,介面都是對於規則的設計入口;規則被維護後會迅速在記憶體中處於執行狀態,當我們需要規則的時候直接被規則引擎執行;

目前來說這樣的系統架構對於高擴充套件性業務系統來說急需,尤其是線上的個性化定製產品,都會有後臺的客服人員或者是資訊人員來根據使用者的需要來設計業務規則;比如某一家企業需要我們為他的ERP系統中的定時發貨邏輯(每天10點,貨物的總額必須大於1000元….等等),隨時修改成希望的引數,這個時候我們要麼去資料庫中修改,要麼去程式的配置檔案修改;

當然好東西是不容易得到的,這塊還沒有成熟的框架支援,如果需要我們得自己去研究實踐了,這裡只是擴充套件一下領域;

1.4.2】精簡模型

模型的設計並不是最終得到一張龐大的蜘蛛網,更不能為了這張蜘蛛網而沾沾自喜,如果你不及時重構的話它很快讓你下不了臺;但是隨著我們的設計時間推移,需求逐漸的變多模型圖慢慢變大應該是正常的才對;問題並不是說大就是錯誤的,而是大了我們就很難控制它了,要時刻讓它在你能控制的範圍內;

模型的重構是迭代的過程,如果只是等到最後再草草的重構一下,僅僅是為了滿足一個理想化的過程而已,那就沒有必要了;重構要實時記在心裡,當一群模型逐漸變的越來越大的時候就要及時對它進行精簡,但是前提是不能破壞模型表達的業務知識;

【實體的關聯】

想要讓模型容易控制,當然首要的是砍斷一些不必要的關聯,從技術角度考慮一下如果一張龐大的關係網讓程式去實現的話會非常的困難,甚至是不可能穩定實現的;所以說領域驅動設計強調的核心精神是分析、設計必須在一個上下文中,通常這需要一個或者一組人員在必須固定的情況下完成,這才能保證領域知識有效的吸收和在team內部傳遞;

那麼如何把一張龐大且複雜的模型網合理的切割成精簡的小模型,而且業務模型不會被破壞;通常我們考慮切割複雜功能的時候都是從功能出發的,包括很多現在系統重構都是這樣的,會出現很多零碎的Function,要說有用吧這些零碎的Fcuntion都需要,要說沒用吧都可以放在一個方法裡,為什麼會這樣?因為我們更本沒有深思問題出在哪裡,或者說更本沒有真正吸收大師們書中的意思;其實問題的重點是我們根本沒有考慮業務模型,功能的劃分都是理所當然的,小粒度的方法抽取,其實只不過從一個坑裡搬到另一個坑裡而已,Service層一眼看上去全是幾乎名稱相似的方法,你根本分不清具有什麼樣的業務邏輯的;

重構的正確方向是按照業務邏輯劃分,必須嚴格按照業務流程來走查場景,當然這裡的重構包括對現有系統的重構,不管你的系統是不是DDD驅動的,都是需要根據業務流程來抽取功能點的,切忌重構的粒度不僅僅是方法而是邏輯Module;

到目前為止我們的所有業務模型都基本上出來了,雖然不是很複雜但是也充分體現出了模型驅動開發的優點,它很便於我們對業務的梳理和對需要的把握。其實到目前我們對系統都沒有進行實質性的編碼或者設計資料庫,在以往這個時候資料庫已經出來了,然後對著一張E-R圖討論系統的需求。但是這裡我們還在討論需求和分析業務的階段,我們用UML模型來與業務專家敲定潛在的需求;

4.3圖

這是最終的模型,好似一張蜘蛛網,這樣的模型雖然能直接反饋出真實的業務場景,但是程式設計無法實現或者說實現起來更本不能用,這就是為什麼DDD反覆強調建模人員一定要懂得程式設計、開發,以往就是因為我們將分析、設計分開來導致領域知識無法傳遞到設計階段,分析的模型其實根本沒有幫助程式在設計階段提供幫助;

這裡我們將把這張網變成程式中可以使用的精簡型的多個小網,而且這些小的網不能破壞模型與現實之間的這種穩定性,其實就是將這複雜的關係能合理、平衡的減少,因為在真實的程式操作當中肯定是有業務縫隙的,也就是業務功能都是一個一個處理的,工作的流程也體現出每一個流程上而不是一股腦的將所有的流程設計到的模型都拉出來;

【確定聚合邊界】

確定聚合邊界是要根據業務來劃分的,那麼如何減少模型的直接的關聯?模型之間的關聯是真實的業務關係,這裡我們需要將它的直接關係改成通過ID的方式關聯。在程式中我們可以很方便的進行類似Id、Key這種唯一標識來作為下一步的輸入資料,因為我們的大部分的資料都在關係型的資料庫中,所以我們首要考慮的是將模型與模型之間的引用關係改成Id的關聯;這種方式有一個好處就是延遲的載入,在單個業務處理中不需要把所有的資料都讀取到記憶體中,而只需要能滿足本次業務處理的即可,因為不管什麼系統都有業務流程的先後順序性;

精簡模型的兩個核心過程:實體的關聯、確定聚合邊界其實是一個過程的兩個考慮點,只有確定了聚合邊界才能將聚合內部與外部的關係砍斷。

我們來調一個現有模型來分析:

4.4圖

目前來說這個點是關聯最多的地方,先來簡單的介紹一下這個模型的大概意思:

【FieldExamination】是表示【每場考試】、【Employee】是表示【員工】、【Subject】是表示【課程類別】列舉,目前我們看的見就這三個;

上面曾說過藍色背景的模型是潛在的模型,這裡我們需要精簡的是【每場考試FieldExamination】模型,上圖中可以看到以它為聚合的關聯有四個,我們就來看【Employee】模型,它是表示所有的學校員工資訊,從【FieldExamination】【Employee】有一個Principal聚合,對於每場考試我們通常都是有一個負責人的,在考試期間可能會去巡查考試紀律包括站考老師是否真的嚴格站考;好像沒有問題啊,就應該有一個複雜人才對啊,但是【Employee】還關聯一些其他模型:

4.5圖

這樣下去一個連結一個,牽一髮而動全身,根本無法使用就連重構都很困難;那麼我們如何尋找要斷開的連結點呢?我們需要考慮【聚合】的範圍,在上圖中的【FieldExamination】中,我們如果需要關聯本場考試的負責人是誰那麼在當【FieldExamination】被讀進到記憶體的時候就被一起關聯出來,但是當我們考慮真實的業務需求的時候到底需不需要將【Employee】帶出來,【Employy】被帶出來的時候就要牽扯到【Employee】所涉及的關聯;

在我們的程式UI層中展現出正在進行的【FieldExamination】時,我們的管理者很希望一眼就能看到本場考試的負責人是誰,而不是在進行一次查詢(不管是非同步還是同步)動作,看來我們還不能將【Employee】與【FieldExamination】斷開;我們看到【Employee】關聯著的是兩個基本的列舉型別,【Employee_Role:員工角色】、【Sex_Type:員工性別】,所以將【Employee】帶出來應該不會有什麼問題;

我們再回頭來看【FieldExamination】關聯,【Employee】這條線我們先放下了,看一下【Subject:科目】關聯:

4.6圖

很簡單的一個列舉型別,關係不大;現在跟【FieldExamination】聚合關係就剩一條了,我們來看看到底需要不需要關聯;

4.7圖

粗線圈出來的是兩個模型的範圍,內部一個小矩形是從【FieldExamination】到【TestBook:考試試卷】的關聯;每場考試都會有一份本場考試的試卷,但是這裡我們如果將試卷帶出來的話,那麼試卷模型也會牽扯到它所關聯的模型;真是的業務需要我們完全可以將它斷開了,對於每場考試的輸出我們不需要知道本場考試的試卷是什麼,只有需要的時候才會去查詢它,這個時候我們可以使用關聯Id來斷開連線;

當然真實的環境肯定是要比這個複雜很多,要平衡很多的業務環節;

1.4.3】模型擴充套件性

到目前為止我們基本上看見了系統設計的大概雛形了,這也是建模的好處,會讓你對系統的所有業務點有一個比較全面的瞭解和深思;那麼接下來我們會面臨著系統設計環節中的重點“模型擴充套件性”那麼什麼叫模型擴充套件性?簡單點講就是“業務邏輯擴充套件性”,如何將業務邏輯抽取出來形成一定程度的可配置性;那麼首要的問題是我們要能夠識別出真正會變化的業務點,而不是盲目的把不重要的或者一棍腦的所有的點都抽出去,這樣很不切實際;

其實模型擴充套件性牽扯到的話題會比較複雜,這也是系統設計中比較難的點,多少年了依然如此,沒有很好的方案可行;目前我總結了可能會對模型擴充套件性起到啟發性的技術“規則引擎”“凍結程式的延續”“後設資料驅動設計”“超程式設計”“領域特定語言”,對於這些技術我們需要很多時間去實踐驗證它們到底如何和DDD結合,要不然也是紙上談兵;有幸本人對這些東西有略微的實踐,但是限於篇幅的問題而且這些東西也不是三兩句話就能講清楚的,這裡算是給大家分享一下這些東西,有興趣的朋友可以去看看,日後一起討論;

那麼這裡要講的是在模型中我們如何在第一層面上剝離出陷在程式碼中的邏輯,將隱式的業務邏輯顯示化,這也是DDD設計過程中的必須要去完成的,要不然日後遇見業務邏輯修改的時候再來重構的話就很麻煩;

我們在現有的模型中找一個業務點來分析抽取它,我一直對時間這個東西比較討厭,發現它在任何時候都可能會被修改配置,所以就它了;在我們【Student學生用例】中就有一個提前交卷的時間限制,可能各個負責人對提前提交時間的態度都不同,每次考試的負責人基本上都不一樣,要不然還不累死;那麼我們模擬一個簡單的過程:

//當前所剩時間一定要小於或者等於10分鐘

if (this.LeaveTime <= 10)

{ 
    //允許提前交卷並且備註提前交卷的記錄

    ExpeditSubmitLog=new ExpeditSubmitLog();

    ……

}

這是最平常的程式碼了,但是一般懂點程式擴充套件性的都會說這裡的“10”分鐘不能寫死了,恩不錯,確實不能寫死了,哪天要改成“20”分鐘的就完了;於是我們將程式碼改成這樣了;

if (this.LeaveTime <= fieldExamination.Advance)

{      //允許提前交卷並且做上提前交卷的記錄

     ExpeditSubmitLog=new ExpeditSubmitLog();

     ……

}

這裡的【fieldExamination】是本場考試物件模型,裡面有一個Advance屬性是用來記錄本場考試能提前多少分鐘交卷的,貌似能通過配置來解決固定時間交卷了,恩 不錯;但是我們在仔細觀察程式碼發現我們將“提前交卷並且做上提前交卷的記錄”業務邏輯散開在程式碼中了,這樣對我們系統維護性來說很有威脅得把它抽出來物件化才行,但是問題並沒有那麼簡單,這裡的邏輯判斷很簡單:“只要滿足fieldExamination.Advance屬性小於this.LeaveTime” 就可以進行下面的操作,你可以將Advance屬性配置成任何值,只要你的需求是正確的;那麼如果這個時候邏輯判斷不再是一個簡單的判斷變成了多重判斷,而且不同的判斷執行不同的邏輯操作,也就是說不同的邏輯判斷跟下面的業務操作是一起的;

程式碼可能是這樣:

if((this.LeaveTime<=fieldExamination.Advance) && fieldExamination.Subject==Subject.English))

{

      //允許提前交卷並且做上提前交卷的記錄

      ExpeditSubmitLog=new ExpeditSubmitLog();

      …… 
}

對於提前交卷的判斷可能有多個條件,每個條件之間可能有著與或非的關係,這其實是【DDD中規約模式】解決的問題,將多個條件判斷都設計成可獨立可組合使用的物件模型,通過策略模式將條件物件依次的組合使用確實解決了條件判斷的問題;

ExpeditSubmitSpecification expeditSubmitSpecification=new ExpeditSubmitSpecification();

expeditSubmitSpecification.Add(new ExpeditSubmitLeaveTime(),ExpressionType.And);

expeditSubmitSpecification.Add(new ExpeditSubmitSubject().ExpressionType.Or);

if(expeditSubmitSpecification.CheckChaining())//進行提前交卷的邏輯判斷;

{

      //允許提前交卷並且做上提前交卷的記錄

      ExpeditSubmitLog=new ExpeditSubmitLog();

      …… 
}

這裡我們已經可以將條件判斷的邏輯物件化了,但是對於提前交卷的業務邏輯還是散開在方法體中的,還記得我們上面曾講過“規則引擎”嗎,將規則的配置在外部設定,我想物件化後這個已經不是不什麼難事了,但是對於到底怎麼提交【提前試卷】的動作可能不只是一種方式:

if(expeditSubmitSpecification.CheckChaining())//進行提前交卷的邏輯判斷;

{

    //允許提前交卷並且做上提前交卷的記錄 
    IExpeditSubmit iexpedit=IoCComponent.Resolve<IExpeditSubmit>();//通過IoC的方式獲取,這裡其實已經將業務邏輯配置化了;

    iexpedit.Submit(ExpeditSubmitContext.CurrentContextInfo);

}

判斷跟執行邏輯是兩個不太相干的過程,不同的判斷可以執行一組邏輯或者不同的判斷執行不同的邏輯,這是根據我們配置來的;

當然這裡只是擴充套件性的介紹一下,本主題在後面的文章中會專門去介紹和研究;

1.5】系統架構設計、DDD分層架構

需求驅動了架構,對於DDD的架構跟以往的架構有著很大的不同,為什麼不同與傳統的架構因為關注的東西從一開始就是不同的;為了將前期分析出來的DDD模型在系統中體現出來,也就是將DDD理論分析徹底落實在程式碼上這需要將業務模型作為重點關注物件,所以架構的焦點從原來的元件型、框架型變成了只關注領域模型的架構;

在我們腦子裡傳統的系統架構都是簡單的分層架構當然我是指目前絕大部分的企業中,也就是傳統的三層架構或者說四層架構,當然用的如何就各式各樣了;架構本身沒有好壞之分只有合適不合適之選;對於我們以前傳統的三層架構,簡單明瞭很好理解,對開發者的要求門檻很低基本上看一遍就知道怎麼寫了;話說回來不管是什麼型別的系統都需要將其進行簡單的分層來分離關注點,在這點上大家都沒有問題,各自取長補短,這是電腦科學多少年驗證的事實,是用來解決複雜問題的通用解決方案;

上面也說了分層架構在每個公司裡面的實現都是不同的,都或多或少的有點特性化在裡面,這也是正確的設計,沒有一勞永逸的架構,但是世界在變所有東西都在變原本我們只關注技術實現的本身忽視了我們本應該重視的業務實現,但是這些技術現在已經很成熟了我們是時候會過頭來關注一下軟體設計的本質了;

從一開始我們就被一個很大的謊言所欺騙著,在我們還不是太懂這個”社會“的時候被“某些“自認為是”專家“的老師將我們引入了一個生存之道的反方向;我們與真正的軟體設計背道而馳,當我們慢慢恢復一個人所應該有的洞察力時我們發現其實世界不是這樣子的,我們是可以活的很好的,我們完全有能力來對付一個龐大的系統,之所以我們以前無能為力是因為我們從一開始就錯了!

架構被多方面原因驅動著, 從技術方面講:硬體、大資料、高併發等等,業務方面:低延遲性、高時效性等等,那麼架構真的是我們所理解的那樣嗎?當然我沒有這個智慧去總結它到底是什麼樣子的,當只有一樣東西的時候我們很難說出它的好與壞,只有當我們將兩個東西在一起比較的時候才能根據互相的參照物進行好與壞的平衡;在計算機領域沒有絕對的好與壞,因為這個世界就沒有絕對的事情;

隨著現在的大資料的到來,我們的架構是否能應付這麼龐大的資料流,是否能抗的住另一方面的衝擊:高併發等等諸如此類的問題,用簡單的一門技術是很難解決一個龐大的問題鏈的,我們需要結眾家之所長來一起對付這些所謂的IT發展的問題流,他們一波又一波的衝擊著我們;

那麼對於DDD的架構它將與傳統架構有著哪些區別,它將為我們帶來哪些的技術和思想,你也許會問為什麼DDD與眾不同,我很高興的告訴你:“因為它面向領域驅動“;用我親身體會來總結一句對DDD的認識:”DDD是系統分析、設計、架構的最佳實踐驗證“,所以我更喜歡稱我們程式設計師最佳驗證者也是最佳實踐者;

1.5.1】傳統分層架構

在我們每個程式設計師的腦子裡都有一個自己的架構模型,每個人的技術底蘊不同架構有強大和簡單的,但是都是在分層架構上延伸出來的;我們先來回顧一下傳統的架構,這裡要解釋一下一個概念就是邏輯架構與物理架構的區別,我們所討論的是邏輯架構也就是程式碼solution中的結構,從這裡面會對映到物理架構中,比如我們的分層架構中都會有cache的功能,在所有的層面上都要進行快取的功能呼叫,那麼肯定是需要將cache的功能進行公共的封裝然後呼叫,但是這個cache的最終是需要部署到伺服器上的,這裡就形成物理架構的對映;

5.1圖

面對這張圖我們在熟悉不過了,基礎框架Common Component裡的所有功能都是在所有層面上共用的,邊界一定要清晰;在Application Layer中有一個很簡單的服務介面是專門用來對外開發功能的,這裡不是SOA只是面向客戶端的WEB介面或者是面向socket的介面都行;

這樣的架構一如既往的被我們使用著,簡單明瞭,但是我們發現它似乎到頭了,問題接踵而來;業務邏輯鋪滿UI層,隨著時間的推移我們的程式碼變的難以維護,系統面臨著進退兩難的地步,這不是技術人員的問題也不是設計的問題只能說是技術在進步;

所以面向領域驅動分層架構可以解決上面提到的問題,當然也會帶來新的問題,不過沒關係世界萬事萬物都是有兩面性的,有對就有錯,有好就有壞,問題始終是要被解決的,關鍵是我們能有勇氣去解決它;

1.5.2】DDD充血型架構

對於DDD的架構會讓我們很新奇,至少我第一次看見它的時候很激動一下子開啟了“軟體工程”的大門,種種問題一下有思路了;當然前提是你曾經埋怨過架構的設計不足帶來的問題,如果系統無法重構那麼它已經80%死掉了,隨著你維護的速度加快它會死的更快;(一定有人有相同的感受)

我們來了解一下DDD的分層架構,我相信你肯定會有很多疑問的,沒關係接受任何新的事物都需要一個過程,關鍵是有充分的理由才行;

5.2】圖

一種方法論誕生之後首要的是要用一種可行的理由說服大眾,那樣才能既眾人之力來完善它;DDD當然也是如此,從五年前的DDD推出之後一隻到現在已經發展的很成熟且很強大;上圖中我們可以很明顯的看出我們弱化了除領域層之外的層,架構來自需求的驅動;

DDD強調我們始終聚焦於軟體的核心,永遠都不要離開業務模型,將所有的規則都封閉在DomainModel中堅決不能讓業務規則洩漏出去;這個時候我們不會關心你是用什麼UI框架,用什麼資料持久化框架,這些都是次要的;價值觀一定不要離開領域模型;

DomainModel不會對外部進行任何呼叫,持久化將在Application Layer中處理,保證DomainModel是一個POJO的物件;在圖的右邊是infrastructure,可以把它理解成基礎設施,可能你會有點想不通,從功能上看不是和上圖中的Common Component一樣嗎?恩 確實差不多,從技術角度講任何東西是差不多的,但是我們考慮的是模型化設計,物件導向設計;問題不在是簡簡單單的技術問題了而是設計思想的層次了,如果不提升設計思想那計算機也不會發展成今天這樣;從抽象的角度講,一切都圍繞著DomainModel,為了支撐DomainModel的執行的都屬於基礎性設施;“設施”一詞很富有高層設計意義,我們已經不在用功能、函式來支撐系統執行了;當然DomainModel的美還需要你親自去接觸才能體會到,這裡只是一個介紹;

隨著DomainModel的不斷龐大,對系統的效能有一定的影響,所以上面曾說過凡事都有兩面性,這樣充血型的架構會將模型變的很肥胖,所以我們必須要找到解決辦法才行,萬幸的是我們站在巨人的肩膀上的;對於這樣的問題DDD.CQRS架構誕生了,為了解決充血性的DDD架構;

(限於篇幅關係再加上這篇文章不是專門講架構的,所以這裡有興趣的朋友可以自己去查詢相關的資料DDD.CQRS架構 、DDD.EDA架構 ;)

1.6】資料儲存設計

一直到現在我們都是在設計、分析物件關係,但是事實是我們的物件都執行在記憶體中才是這一切的本質;物件只有被載入到記憶體中才能讓他們活動起來,但是物件始終是要被持久化儲存起來的,也就是離不開資料儲存技術;目前我們普遍使用關係型資料庫來儲存資料,當然也可以放在任何NoSql資料庫中;當然最優的方案是In-memory,將DomainModel直接快取在記憶體中,但是這項技術目前不是很成熟或者說對他掌握的人很少;按照DDD的架構設計我們將不直接依賴於資料儲存框架,不會受限於資料持久化的約束;當然我們完全可以將DDD儲存在XMLDOM中,讓後將XMLDOM快取在Memory中,本來DOM就有很強的表現能力,從XAML就能看出DOM在後面將會在很多地方用到;

大膽的構想XML將被用作於領域特定語言上,對特性領域的抽象將不在侷限於某種程式語言;語言是用來交流,原本的程式語言是程式設計師用來跟計算機交流的語言,但是技術在發展,程式語言將進一步被抽象被掩蓋在底層,很久以後我們將不在需要直接編寫程式語言,將通過與領域專家的合作開發出符合特定領域的語言,將用這種專門的語言來生產軟體;

作為Microsoft.NET平臺的我們,如果對領域特定語言有興趣的朋友可以推薦看一下這本書《VisualStudent DSL 工具特定領域開發指南》,內容比較深,對分析設計架構建模 均要有一定程度的熟悉,不過可以作為技術研究嘗試一下;

資料儲存設計需要結合實際的需求和架構來的,大型電子商務和企業基本的ERP在資料儲存設計上肯定是有所偏重的,比如電子商務在架構設計上可能允許低延遲性、資料最終一致性,而ERP可能需要響應及時性,資料實時一致性,這本身就需要平衡的;

分散式領域的CAP定理 大家應該都有所聽聞,分散式系統必須要平衡好三要素:Consistency(一致性), 資料一致更新,所有資料變動都是同步的;Availability(可用性), 好的響應效能;Partition tolerance(分割槽容錯性) 可靠性;

定理:任何分散式系統只可同時滿足二點,沒法三者兼顧。 忠告:架構師不要將精力浪費在如何設計能滿足三者的完美分散式系統,而是應該進行取捨。

——引自(解道.板橋里人)

其實這裡的資料儲存設計已經不是一個建立Table的那麼簡單了,現在動不動就大資料量,高併發所以我們如何將DomainModel放入儲存設施;這確實很難,設計的不好將對後面的系統整體架構帶來難以擴充套件影響,當然這也不是本篇文章討論的問題我也不具備這樣的能力;這裡要討論的是如何對映DomainModel到關係型資料庫;關聯式資料庫是面向關係模型,而我們的DomainModel是複雜的物件導向模型,如何在這兩者之間很平滑的對映,我們需要ORM框架的支援;沒有很好的ORM框架很難解決一些純技術問題,這裡我們當然是使用.NETEntityFramework框架來支撐(後面本部落格將有一個系列詳細的深入解析EntityFramework框架的文章),當然也可以使用其他的ORM框架,開源的也好、免費的也好;每種框架的對映原理不同,這裡就不一一講解了,使用EntityFrmework對映其實很簡單的,網上也有很多使用文章;

1.6.1】模型與關係資料之間的平衡

在文章中到處充滿著模型一詞,模型是具體事物的抽象表現,他是人最直觀的接收方式;那麼畢竟模型是虛擬的東西,只是一種幫助理解的描述而已,將模型等價的持久化到任何資料儲存容器中都需要能平衡的進行模型與資料結構之間的對映才行,不管你是SQL也好還是NOSQL也好,都需要事先構造好這種對映關係才行;模型的設計理論是物件導向,而大部分的資料來源儲存容器基本上都是SQL資料庫,如何很好的將模型在資料庫中持久化這在架構上也需要一定的要求和調整;

【繼承深度要控制好】

由於繼承很難在關係型模型中體現,資料庫需要很牽強的表達這種關係(你可以使用EntityFramework的Model  First試一下);所以繼承層次多了很難在Repository中解決,當然也不是說不用繼承,只是說層次不要多,不能像設計框架那樣隨意的設計類,Domain  Model 儘可能的簡單;

【儘量避開Reposiroty在User\Role中】

在我們使用DCI架構並且使用行為驅動設計來捕獲系統的需求的時候,傳統架構中的物件將在系統中不復存在了,系統中充滿了場景角色資料,讓物件導向上了一層樓,更讓面向DDD的分層架構上了一層高樓;如果將很多與Repository相關的行為放入角色、使用者物件中將帶來很多耦合,當然只有去做一次才能真正體會到,這裡我只是總結一下;

總結:本文很長,花了我很多時間,不過很值;希望本篇文章能幫大家簡單的掃盲一下關於DDD的相關技術,文章沒有太多高深的技術,只是一些我們不太接觸的理論技術而已,其實我們真的有必要尋找一條正確的軟體工程道路,物件導向分析設計如果脫離編碼那一點意義都沒有了,至少目前確實是這樣的;

那麼又有多少人真正知道問題在哪裡呢?很可笑,我也不知道,但是我可以很明確的告訴大家,DDD是尋找解決問題的思路,也是通往光明的正確道路,希望對DDD有興趣的朋友可以去專研它,就算當成興趣愛好也行,千萬別視而不見因為將在下一次的技術革命中DDD會大面積爆發;謝謝;

 

相關文章