模型被用來描述人們所關注的現實或想法的某個方面。模型是一種簡化。它是對現實的解釋 —— 把與解決問題密切相關的方面抽象出來,而忽略無關的細節。
每個軟體程式是為了執行使用者的某項活動,或是滿足客戶的某種需求。這些使用者應用軟體的問題區域就是軟體的領域。
一些領域涉及物質世界,例如,機票預定程式的領域中包括飛機乘客。有些領域則是無形的,例如,會計程式的金融領域。
為了建立真正能為使用者所用的軟體,開發團隊必須運用一整套與這些活動有關的知識體系。所需知識的廣度可能令人望而生畏,龐大複雜的資訊也可能超乎想象。模型正是解決此類資訊超載問題的工具。模型這種知識形式對於知識進行了選擇性的簡化和有意的結構化。適當的模型可以使人理解資訊的意義,並專注於問題。
領域模型並非某種特殊的圖,而是這種所要傳達的思想。它絕不單單是領域專家頭腦中的知識,而是對這類知識嚴格的組織且有選擇的抽象。
領域模型並不是要儘可能建立一個符合「現實」的模型。即使是對具體、真實世界中的事物進行建模,所得到的模型也不過是對事物的一種模擬。出於某種目的概括地反應現實。
模型在領域驅動設計中的作用
在領域模型的設計中,3個基本用途決定了模型的選擇。
(1)模型和設計的核心互相影響。正是模型與實現之間的緊密聯絡才使模型變得有用,並確保我們在模型中所進行的分析能夠轉化為最終產品(即一個可執行的程式)。
(2)模型是團隊所有成員使用的通用語言的中樞。由於模型與實現之間的關聯,開發人員可以使用語言來討論程式。可以在無需翻譯的情況下與領域專家進行溝通。
(3)模型是濃縮的知識。模型是團隊一致認同的領域知識的組織方式和重要元素的區分方式。透過我們如何選擇術語、分解概念、以及將概念聯絡起來,模型記錄了我們看待領域的方式。
軟體的核心
軟體的核心是為使用者解決領域相關的問題的能力。所有其他特性,不管有多麼重要,都要服務於這個基本目的。當領域很複雜時,這是一項艱鉅的任務。開發人員必須鑽研領域以獲取業務知識。必須磨礪其建模技巧,並精通領域設計。
1.消化知識
一.有效建模的要素
(1)模型和實現的繫結。最初的原型雖然簡陋,但它在模型和實現之間建立了早期連結,而且在所有後續的迭代中我們一直維護該連結。
(2)建立一種基於模型的語言。
(3)開發一個蘊含豐富知識的模型。物件具有行為和強制性規則。模型並不僅僅是一種資料模式,它還是解決複雜問題不可或缺的部分。模型包含各種型別的知識。
(4)提煉模型。
(5)頭腦風暴和實驗。語言和草圖,再加上頭腦風暴活動,將我們的討論變成 「模型實驗室」 ,在這些討論中可以演示、嘗試和判斷上百種變化。當團隊走查場景時,口頭表達本身就可以作為所提議的模型的可行性測試,因為人們聽到口頭表達後,就能立即分辨出它是表達得清楚、簡捷,還是表達得很笨拙。
二.知識消化
高效的領域建模人員是知識的消化者。他們在大量資訊中探尋有用的部分。他們不斷嘗試各種資訊組織方式,努力尋找對大量資訊有意義的簡單檢視。很多模型在嘗試後被放棄或改造。只有找到一組適用於所有細節的抽象概念後,工作才算完成。這一精華嚴謹地表示了所發現的最為相關的知識。
知識消化並非一項孤立的活動,它一般是在開發人員的領導下,由開發人員和領域專家組成的團隊來共同共同作業。他們共同收集資訊,並通過消化而將它組織為有用的形式。
模型聚焦於需求分析。它與程式設計和設計緊密互動。模型對理解領域必須是切實可行的。
三.持續學習
高效率的團隊需要有意識地積累知識,並持續學習。對於開發人員來說,這意味著既要完善技術知識,也要培養一般的領域建模技巧,也包括認真學習他們正在從事的特定領域的知識。
四.知識豐富的設計
業務活動和規則如同所涉及的實體一樣,都是領域的核心,任何領域都有各種類別概念。知識消化所產生的模型能夠反映出對知識的深層理解。
當我們的建模不再侷限於尋找實體和值物件時,我們才能充分吸取知識,因為業務規則之間可能會存在不一致。領域專家需要反覆研究所有規則、解決規則之間的矛盾以及常識來彌補規則的不足等一系列工作。
五.深層模型
有用的模型很少停留在表面。隨著對領域和應用程式需求的理解逐步加深,我們往往會丟棄那些最初看起來很重要的表面元素,或者切換它們的角度。
2.交流與語言的使用
領域模型可成為軟體專案通用語言的核心。該模型是一組得自於專案人員頭腦中的概念,以及反映了領域深層含義的術語和關係。這些術語和相互關係提供了模型預言的語意,雖然語言是為領域量身客製化的,但就技術開發而言,其依然足夠精確。正是這條至關重要的紐帶,將模型與開發活動結合在一起,並使模型與程式碼緊密繫結。
這種基於模型的交流並不侷限於 UML 圖。為了有效地使用模型,需要充分利用各種交流手段。基於模型的交流提高了書面檔案的效用,也提高了敏捷過程中再度強調的非正式圖表和交談的效用。它還通過程式碼本身及對應的測試促進了交流。
在專案中,語言的使用很微妙,但卻至關重要。
一.模式:Ubiquitous Language
要想建立一種靈活、蘊含豐富知識的設計,需要一種通用的、共用的團隊語言,以及對語言不斷的試驗——然而,軟體專案上很少出現這樣的試驗。
所有翻譯的開銷,連帶著誤解的風險,成本很高。專案需要一種公共語言,這種語言要比所有語言的最小公分母健壯得多。通過團隊的一致努力,領域模型可以成為這種公共語言的核心,同時將團隊溝通與軟體實現緊密聯絡到一起。該語言將存在於團隊工作中的方方面面。
Ubiquitous Language(通用語言)的詞彙包括類和主要操作的名稱。語言中的術語,有些用來討論模型中意境明確的規則,還有一些規則來自施加於模型上的高階組織原則。
為了解釋和給出更廣泛的上下文,領域專家的語言會超出 Ubiquitous Language 的範圍。但在模型對應的範圍內,他們應該使用 Ubiquitous Language,並在發現不合適、不完整或錯誤之處後要引起注意。通過大量使用基於模型的語言,並且不達順暢不罷休,我們可以逐步得到一個完整的、易於理解的模型,它由簡單元素組成,並通過組合這些簡單元素表達複雜的概念。
要認識到,Ubiquitous Language 的更改就是對模型的修改。
有了 Ubiquitous Language ,模型就不僅僅是一個設計工作了。它成為開發人員和領域專家共同完成的每項工作中不可或缺的部分。語言以動態形式傳遞知識。使用這種語言進行討論能夠呈現圖和程式碼背後的真實含義。
Ubiquitous Language 是那些以非程式碼形式呈現的設計的主要載體,這些包括把整個系統組織在一起的大尺度結構,定義了不同系統和模型之間關係的限界上下文,以及在模型和設計中使用的其他模式。
二.「大聲地」 建模
改善模型的最佳方法之一就是通過對話來研究,試著說出可能的模型變化中的各種結構。這樣不完善的地方很容易被聽出來。
使用單詞和短語是極為重要的——其將我們的語言能力用於建模工作,這就如同素描對於表現視覺和空間推理一樣重要。我們既要利用系統性分析和設計方面的分析能力,也要利用對程式碼的神祕「感覺」。這些思考方式互為補充,要充分利用它們來找到有用的模型和設計。在所有這些方式中,語言上的試驗通常是最容易被忽視的。
討論系統時要結合模型,使用模型元素及其互動來描述場景,並且按照模型允許的方式將各種概念結合到一起。找到更簡單的方式來講出你要講的話,然後將這些新的想法應用到圖和程式碼中。
三.一個團隊,一種語言
開發人員會使用領域專家無法理解的技術術語。使用者也會用開發人員無法理解的、超出應用程式範疇的專用術語。這些都是對語言的擴充套件。但在這些語言擴充套件中,同一領域的相同詞彙不應該反映不同的模型。
四 檔案和圖
一邊畫圖一邊用語言來豐富它們的意義,或者討論時進行解釋。
簡單、非正式的UML圖能夠維繫整個討論。繪製一幅包含當前問題最關鍵的 3~5 個物件的圖,這樣每個人都可以集中注意力。所有人就物件關係達成一致,更重要的是,他們將使用相同的物件名稱。當人們嘗試不同的想法時,圖也隨之改變,草圖在某種程度上可以反應討論的變化,這是討論中真正重要的部分。
當通過UML圖表示整個模型或設計時,會因為過於細緻導致 「只見樹木,不見森林」。可以把細節(例如約束和斷言)在圖中用文字加進來。
圖是一種溝通和解釋手段,它們可以促進頭腦風暴。簡潔的小圖能夠很好地實現這些目標,而涵蓋整個物件模型的綜合性大圖反而失去了溝通和解釋能力,因為它們將讀者淹沒在大量細節之中,加之這些圖也缺乏目的性。鑑於此,我們應避免使用包羅萬象的物件模型圖,甚至不能使用包含所有細節的UML資料儲存庫。相反,應使用簡化的圖,圖中只包含物件模型的重要概念——這些部分對於理解設計至關重要。
設計的重要細節應該在程式碼中體現出來。良好的實現應該是透明的,清楚地展示其背後的模型。互為補充的圖和檔案能夠引導人們將注意力放在核心要點上。
務必要記住模型不是圖。圖的目的是幫助表達和解釋模型。程式碼可以充當設計細節的儲存庫。
1. 書面檔案設計
口頭交流可以解釋程式碼的含義,因為可作為程式碼精確性和細節的補充。但書面檔案是必不可少的,任何規模的團隊都需要它來提供穩定和共用的交流。
兩條用於評估檔案的總體原則:
(1)檔案應作為程式碼和口頭交流的補充
檔案不應再重複表示程式碼已經明確表達出的內容。程式碼已經含有各個細節,它本身就是一種精確的程式行為說明。
其他檔案應該著重說明含義,以便使人們能夠深入理解大尺度結構,並將注意力集中在核心元素上。
(2)檔案應當鮮活並保持最新
通過將檔案減至最少,並且主要用它來補充程式碼和口頭交流,就可以避免檔案與專案脫節。
2.完全依賴可執行程式碼的情況
良好的程式碼具有很強的表達能力,但它所傳遞的資訊不能確保是準確的。一段程式碼所產生的實際行為是不會改變的。測試中的斷言是嚴格的,但變數和程式碼組織方式所表達出來的意思未必嚴格。好的程式設計風格會盡力使這種聯絡直接化,但其仍然主要靠開發人員的自律。
儘管程式碼可能產生誤導,但它仍然比其他檔案更基礎。要想利用當前的標準技術使程式碼所傳達的訊息與它的行為和意圖保持一致,需要紀律和思考設計的特定方式(後面會詳細介紹)。
五 解釋性模型
在實現、設計和團隊交流中需要使用同一個模型作為基礎。如果各有各的模型,將會造成危害。
對設計起到推動作用的模型是領域的一個檢視,但為了學習領域,還可以引入其他檢視,這些檢視只作用傳遞一般領域知識的教學工具。出於此目的,人們可以使用與軟體設計無關的其他種類模型的圖片或文字。
使用其他模型的一個特殊原因是範圍。驅動軟體開發過程的技術模型必須經過嚴格的精簡,以便用最小化的模型來實現其功能。而解釋性模型則可以包含那些提供上下文的領域方面——這些上下文用於澄清範圍更窄的模型。
解釋性模型提供了一定的自由度,可以專門為某個特殊主體客製化一些表達力更強的風格。領域專家在一個領域中所使用的視覺隱喻通常呈現了更清晰的解釋,這可以教給開發人員領域知識,同時使領域專家們的意見更一致。
解釋性模型不必是物件模型,而且最好不是。
3.繫結模型和實現
領域驅動設計要求模型不僅能夠指導早期的分析工作,還應該成為設計的基礎。
3.1 模式: Model-Driver-Design
嚴格按照基礎模型來編寫程式碼,能夠使程式碼更好地表達設計含義,並且使模型與實際的系統相契合。
那些壓根就沒有領域模型的專案,僅僅通過編寫程式碼來實現一個又一個的功能,無法利用知識消化和溝通所帶來的好處。如果涉及複雜的領域就會使專案舉步維艱。
模型和程式設計之間的聯絡可能在很多情況下被破壞,但是二者的這種分離往往是有意而為之的。很多設計方法都是提倡使用完全脫離於程式設計的分析模型,並且通常這二者是由不同的人員開發的。之所以稱其為分析模型,是因為它是對業務領域進行分析的結果,它在組織業務領域中的概念時,完全不去考慮自己在軟體系統中將會起到的作用。分析模型僅僅是理解工具,人們認為把它與程式實現聯絡在一起無異於攪渾一池清水。隨後的程式設計與分析模型之間可能僅僅保持一種鬆散的對應關係。在建立分析模型時並沒有考慮程式設計的問題,因此分析模型很有可能無法滿足程式設計的需求。
這種分析中會有一些知識消化的過程,但是在編碼開始後,如果開發人員不得不重新對設計進行抽象,那麼大部分的領域知識就會被丟棄。如此一來,就不能保證在新的程式設計中還能保留或重現分析人員所獲得的並且嵌入在模型中的領域知識。到了這一步,要維護程式設計和鬆散連線的模型之間的對應關係就很不合算了。
無論是什麼原因,軟體的設計如果缺乏概念,那麼軟體充其量不過是一種機械化的產品——只實現有用的功能卻無法解釋操作的原因。
如果整個程式設計或者其核心部分沒有與領域模型相對應,那麼這個模型就是沒有價值的,軟體的正確性也值得懷疑。同時,模型和設計功能之間過於複雜的對應關係也是難於理解,在實際專案中,當設計改變時也無法維護這種關係。若分析與設計之間產生嚴重分歧,那麼在分析和設計活動中所獲得的知識就無法彼此共用。
分析工作一定要抓住領域內的基本概念,並且用易於理解和易於表達的方式描述出來。
Model- Driver-Design(模型驅動設計)不再將分析模型和程式設計分離開,而是尋求一種能夠滿足這兩方面需求的單一模型。不考慮純粹的技術問題,程式設計中的每個物件都反映了模型中所描述的相應概念。這就要求我們以更高的標準來選擇模型,因為它必須同時滿足兩種完全不同的目標。
有很多方法可以對領域進行抽象,也有很多種設計可以解決應用程式的問題。因此,繫結模型和程式設計是切實可行的。但這種繫結不能夠因為技術考慮而削弱分析的功能,也不能接受那些只反應了領域概念卻捨棄了軟體設計原則的拙劣設計。模型和設計的繫結需要的是在分析和程式設計階段都能發揮良好作用的模型。如果模型對於程式的實現來說顯得不太實用時,我們必須重新設計它。而如果模型無法忠實地描述領域的概念,也必須重新設計它。這樣,建模和程式設計就結合為一個統一的迭代開發過程。
軟體系統各個部分的設計應該忠實地反映領域模型,以便體現出這二者之間的明確對應關係。我們應該反覆檢查並修改模型,以便軟體可以更加自然地實現模型,即使想讓模型反映出更深層次的領域概念時也應如此。我們需要的模型不但應該滿足這兩種需求,還應該能夠支援健壯的 Ubiquitous Language(通用語言)。
從模型中獲取用於程式設計和基本職責分配的術語。讓程式程式碼成為模型的表達,程式碼的改變可能是模型的改變。而其影響勢必要波及接下來相應的專案活動。
完全依賴模型的實現通常需要支援建模正規化的軟體開發工具和語言,比如物件導向的程式設計。
3.2 建模正規化和工具支援
為了使 Model- Driver-Design 發揮作用,一定要在可控範圍內嚴格保證模型與設計之間的一致性。要實現這種嚴格的一致性,必須要運用由軟體工具支援的建模正規化,它可以在程式中直接建立模型的概念。
物件導向程式設計之所以功能強大,是因為它基於建模正規化,並且為建模構造提供了實現方法。
在 Model- Driver-Design 中,建模正規化是邏輯的,而模型則是一組邏輯規則以及這些規則所操作的事實。
建模正規化:
1.第一規格化(1NF):原子性,列不能再分了,每一列只包含一個屬性,所有屬性都是同一型別,但集合、陣列、記錄等非原子資料項不是,即當一個實體中的一個屬性有多個值時,必須拆分成不同的屬性。這是所有關聯式資料庫最基本的要求;
2.第二正規化(2NF):唯一性,一個表只描述一件事,表中所有的列都必須依賴主鍵,沒有一列可以與主鍵無關,即一個表只描述一件事;
3.第三正規化(3NF):每一列都與主鍵直接相關,依賴主鍵屬性不能傳遞屬性。3NF在2NF的基礎上,消除了非主屬性對程式碼傳遞函數的依賴。也就是說,如果非主屬性對程式碼的傳遞函數存在依賴,則不滿足3NF的要求。
3.3 為什麼模型對使用者至關重要
從理論上講,可以向用戶展示任何一種系統檢視,而不管底層如何實現。但實際上系統上下層結構的不匹配輕則導致誤解,重則產生BUG。
Model- Driver-Design 要求只使用一個模型(在任何一個上下文中都是如此)。大部分的設計建議和例子都只針對將分析模型和設計模型分離的問題,但是這裡的問題涉及了另外一對不同的模型:使用者模型和設計/實現模型。
如果程式設計基於一個能夠反映出使用者和領域專家所關心的基本問題的模型,那麼與其他設計方式相比,這種設計可以將其主旨更明確地展示給使用者。讓使用者瞭解模型,將使他們有更多機會挖掘軟體的潛能,也能使軟體的行為合乎情理、前後一致。
3.4 模式:Hands-On Modeler
人們總是把軟體開發比喻成製造業。這個比喻的一個推論是:經驗豐富的工程師做設計工作,而技能水平較低的勞動力負責組裝產品。這種做法使許多專案陷入困境,原因很簡單——軟體開發就是設計。雖然開發團隊中的每個成員都有自己的職責,但是將分析、建模、設計和程式設計工作過度分離會對 Model- Driver-Design 產生不良影響。
如果編寫程式碼的人員認為自己沒必要對模型負責,或者不知道如何讓模型為應用程式服務,那麼這個模型就和程式沒有任何關聯。如果開發人員沒有意識到改變程式碼就意味著改變模型,那麼他們對程式的重構不但不會增強模型的作用,反而會削弱它的效果。同樣,如果建模人員不參與到程式實現的過程中,那麼對程式實現的約束就沒有切身的感受,即使有,也會很快忘記。 Model- Driver-Design 的兩個基本要素(即模型要支援有效的實現並抽象出關鍵的領域知識)已經失去了一個,最終模型將變得不再實用。最後一點,如果分工阻斷了設計人員與開發人員之間的共同作業,使他們無法轉達實現 Model- Driver-Design 的種種細節,那麼經驗豐富的設計人員則不能將自己的知識和技術傳遞給開發人員。
Hands-On Modeler(親身實踐的建模者)並不意味著團隊成員不能有自己的專業角色。但是如果把 Model- Driver-Design 中密切相關的建模和實現這兩個過程分離開,則會產生問題。
整體設計的有效性有幾個非常敏感的影響因素——那就是細粒度的設計和實現決策的質量和一致性。在 Model- Driver-Design 中,程式碼是模型的表達,改變某段程式碼就改變了相應的模型。程式設計師就是建模人員,無論他們是否喜歡。所以在開始專案時,應該讓程式設計師完成出色的建模工作。
因此:
任何參與建模的技術人員,不管在專案中的主要職責是什麼,都必須花時間瞭解程式碼。任何負責修改程式碼的人員則必須學會用程式碼來表達模型。每一個開發人員都必須不同程度地參與模型討論並且與領域專家保持聯絡。參與不同工作的人必須有意識地通過 Ubiquitous Language 與接觸程式碼的人及時交換關於模型的想法。
將建模和程式設計過程完全分離是行不通的,然而大型專案依然需要技術負責人來協調高層次的設計和建模,並幫助做出最困難或最關鍵的決策。後面將介紹這種決策。
Model- Driver-Design 利用模型來為應用程式解決問題。專案組通過知識消化將大量雜亂無章的資訊提煉成實用的模型。而 Model- Driver-Design將模型和程式實現過程緊密結合。 Ubiquitous Language 則成為開發人員、領域專家和軟體產品之間傳遞資訊的渠道。
最終的軟體產品能夠在完全理解核心領域的基礎上提供豐富的功能。
Model- Driver-Design 離不開詳盡的決策設計,後面將介紹這些決策。