1. 程式人生 > >《領域驅動設計》學習筆記

《領域驅動設計》學習筆記

【第一部分】運用領域模型


第1章:消化知識

有效的建模要素

(1)模型和實現的繫結

(2)建立了一種基於模型的語言

(3)開發一個蘊含豐富知識的模型

(4)提煉模型

(5)頭腦風暴和實驗

 

【學習心得】:千萬不要用自己有限的思維規劃完整的圖形,持續學習、消化、輸出(討論)、沉澱,所有道理都是一致的。

 

第2章:交流語言與使用

模式:UBIQUITOUS LANGUAGE(通用語言)


術語的交集產生了UBIQUITOUS LANGUAGE

想要建立種靈活的、蘊含豐富知識的設計,需要一種通用的、共享的團隊語言,以及對語言不斷的試驗——然而,軟體專案上很少出現這樣的試驗。

如果語言支離破碎,專案必將遭遇嚴重問題。領域專家使用他們自己的術語,而技術團隊所使用的語言則經過調整,以便從設計角度討論領域。日常討論所使用的術語與程式碼(軟體專案的最重要產品)中使用的術語不一致,甚至同一個人在講話和寫東西時使用的言語也不一致,這導致的後果是,對領域的深刻表達常常稍縱即逝,根本無法記錄到程式碼或文件中。翻譯使得溝通不暢,並削弱了知識消化。然而任何一方的語言都不能成為公共語言,因為它們無法滿足所有的需求。

 

【學習心得】:在自己有限的專案經驗裡,說溝通成本佔據專案總成本的八成都不為過,就像本書一開始的重點,就是無處不在的語言。這語言可以是人話、可以是圖形、可以是表格,重點在於可以幫助專案高質量高效率的落地。這裡引用歌德的一句話:“世界上的誤解和懈怠,也許比奸詐和惡意更誤事”。

 

第3章:繫結模型和實現

模式:MODEL-DRIVEN-DESIGN

 


模型-正規化-設計

嚴格按照基礎模型來編寫程式碼,能夠使程式碼更好地表達設計含義,並且使模型與實際的系統想契合。

如果整個程式設計或者其核心部分沒有與領域模型相對應,那麼這個模型就是沒有價值的,軟體的正確性也值得懷疑。同時,模型和設計功能之間過於複雜的對應關係也是難於理解的,在實際專案中,當設計改變時也無法維護這種關係。若分析與設計之間產生嚴重分歧,那麼在分析和設計活動中所獲得的知識就無法彼此共享。

軟體系統各個部分的設計應該忠實地反映領域模型,以便體現出這二者之間的明確對應關係。我們應該反覆檢查並修改模型,以便軟體可以更加自然地實現模型,即使想讓模型反映出更深層次的領域概念時也應如此。我們需要的模型不但應該滿足這種需求,還應該能夠支援健壯的UBIQUITOUS LANGUAGE(通用語言)。

從模型中獲取用於程式設計和基本職責分配的術語。讓程式程式碼成為模型的表達,程式碼的改變可能會是模型的改變。而其影響勢必要波及接下來相應的專案活動。

 

【學習心得】:模型、正規化與設計的基本認知時候所有溝通的基石,無論是技術人員還是領域業務人員都有必要對這些知識有一個深入的理解,切記把自己侷限在自己的細節當中,用人話講就是釘子思維。對其他工作小組的認識是一種促進大家更好合作的責任心態度,還是那句話,用巨集觀的視野做微觀的事情。

 

【 第二部分 】模型驅動設計的構造塊


第4章:分離領域

模式:LAYERED ARCHTECTURE


分層模式

想要創建出能夠處理複雜任務的程式,需要做到關注點分離——使設計中的每個部分得到單獨的關注。在分離的同時,也需要維持系統內部複雜的互動關係。

分層的價值在於每一層都只代表程式中的某一特定方面的。這種限制使每個方面的設計都更具內聚性,更容易理解。

而領域層是模型的精髓。領域模型是一些列概念的集合。“領域層”則是領域模型以及所有與其直接相關的設計元素的表現,他由業務邏輯的設計和實現組成。在MODEL-DRIVEN-DESIGN中,領域層的軟體構造反映出了模型概念。

 

【學習心得】:分離意味著原始的複雜,這是發展的一個趨勢,技術的進步往往在於精細化的分工,而這種分層的另一個好處是,分離核心,聚焦問題。

 

第5章:軟體中所表示的模型

模式:ENTITY(又稱為REFERENCE OBJECT)

一些物件主要不是由它們的屬性定義的。它們實際上表示了一條“標識線”(A Thread of Identity),這條線跨越時間,而且常常經歷多種不同的表示。有時,這樣的物件必須與另一個具有不同屬性的物件相匹配。而有時一個物件必須與具有相同屬性的另一個物件區分開。錯誤的標識可能會破壞資料。

當一個物件由其標識(而不是屬性)區分時,那麼在模型中應該主要通過標識來確定該物件的定義。是類定義變得簡單,並集中關注生命週期的連續性和標識。定義一種區分每個物件的方式,這種方式應該與其形式和歷史無關。要格外注意那些需要通過屬性來匹配物件的需求。在定義標識操作時,要確保這種操作為每個物件生成唯一的結果,這可以通過附加一個保證唯一性的符號來實現。這種定義標識的方法可能來自外部,也可能是由系統建立的任意標示符,但它在模型中必須是唯一的標識。模型必須定義“符合什麼條件才算是相同的事物”。

 

模式:VALUE OBJECT

很多物件沒有概念上的標識,它們描述了一個事務的某種特徵。而這類用於描述領域的某個方面而本身沒有概念標識的物件稱為VALUE OBJECT(值物件)。

當我們只關心一個模型元素的屬性時,應把它歸類為VALUE OBJECT。我們應該使這個模型元素能夠表示出其屬性的意義,併為它提供相關功能。VALUE OBJECT應該是不可變的。不要為它分配任何標識,而且不要把它設計成像ENTITY那麼複雜。

 

模式:SERVICE

有時,物件不是一個事物。在某些情況下,最清楚、最實用的設計會包含一些特殊的操作,這些操作從概念上講不屬於任何物件。與其把它們強制地歸於哪一類,不如順其自然地在模型中引入一種新的元素,這就是SERVICE(服務)。

所謂SERVICE,它強調的是與其他物件的關係。與ENTITY和VALUE OBJECT不同,它只是定義了能夠為客戶做什麼。SERVICE往往是一個一活動來命名,而不是以一個ENTITY來命名,也就是說,它是動詞而不是名詞。

好的SERVICE有以下3個特徵:

(1)與領域概念相關的操作不是ENTITY或VALUE OBJECT的一個自然組成部分。

(2)介面是根據領域模型的其他元素定義的。

(3)操作是無狀態的。

當領域中的某個重要的過程或轉換操作不是ENTITY或VALUE OBJECT的自然職責時,應該在模型中新增一個作為獨立介面的操作,並將其宣告為SERVICE。定義介面時要使用模型語言,並確保操作名稱是UBIQUITOUS LANGUAGE中的術語。此外,應該使SERVICE成為無狀態的。

SERVICE與孤立的領域層:這種模式只重視那些在領域中具有重要意義的SERVICE,但SERVICE並不只是在領域中使用。我們需要注意區分屬於領域層的SERVICE和那些屬於其他層的SERVICE,並劃分責任,以便將它們明確地區分開。


將SERVICE劃分到各層中(資金轉賬 示例)  

模式:MODULE(也稱為PACKAGE)

MODULE是一個傳統的、較成熟的設計元素。雖然使用模組有一些技術上的原因,但主要原因卻是“認知超載”。MODULE為人們提供了兩種觀察模型的方式,一是可以在MODULE中檢視細節,而不會被整個模型淹沒,二是觀察MODULE之間的關係,而不考慮其內部細節。

每個人都會使用MODULE,但卻很少有人它們當作模型中的一個成熟的組成部分。程式碼按照各種各樣的類別進行分解,有時是按照技術架構來分割的,有時是按照開發人員的任務分工來分割的。甚至那些從事大量重構工作的開發人員也傾向於使用專案早期形成的一些MODULE。

眾所周知,MODULE之間應該是低耦合的,而在MODULE的內部則是高內聚的。耦合和內聚的解釋使得MODULE聽上去像是一種技術指標,彷彿根據關聯和互動的分佈情況來機械地判斷它們。然而,MODULE並不僅僅是程式碼的劃分,而且也是概念的劃分。一個人一次考慮的事情是有限的(因此才要低耦合)。不連貫的思想和“一鍋粥”似的思想同樣難於理解(因此才要高內聚)。

因此:選擇能夠描述系統的MODULE,並使之包含一個內聚的概念集合。這通常會實現MODULE之間的低耦合,但如果效果不理想,則應尋找一種更改模型的方式來消除概念之間的耦合,或者找到一個可以作為MODULE基礎的概念(這個概念先前可能被忽視了),基於這個概念組織的MODULE可以以一種有意義的方式將元素集中到一起。找到一種低耦合的概念組織方式,從而可以相互獨立地理解和分析這些概念。對模型進行精化,直到可以根據高層領域概念對模型進行劃分,同時相應的程式碼也不會產生耦合。MODULE的名稱應該是UBIQUITOUS LANGUAGE中的術語。MODULE及其名稱應反映出領域的深層知識。

 

【學習心得】:每一個概念或方法,都有其含義來源和出處。學會尋找資訊的源頭,學會給自己的認知指明來源和出處,具備嚴謹的邏輯思維,科學地學習和認知,是一切成功的基礎。杜絕垃圾二手資訊資料,杜絕自我侷限性拍腦袋的認知決策過程。

 

第6章:領域物件的什麼週期


領域物件的生命週期  

模式:AGGREGATE

在具有複雜關聯的模型中,要想保證物件更改的一致性是很困難的。不僅互不關聯的物件需要遵守一些固定規則,而且緊密關聯的各組物件也要遵守一些固定規則。然而,過於謹慎的鎖定機制又會導致多個使用者之間毫無意義地互相干擾,從而使系統不可用。

固定規則(invariant)是指在資料變化時必須保持一致性規則,其涉及AGGREGATE成員之間的內部關係。而任何跨越AGGREGATE的規則將不要求每時每刻都保持最新狀態。通過事件處理,批處理或其他更新機制,這些依賴會在一定時間內得以解決。但在每個事務完成時,AGGREGATE內部所應用的固定規則必須得到滿足。為了實現這個概念上的AGGREGATE,需要對所有事務應用一組規則:

□ 根ENTITY具有全域性標識,它最終負責檢查固定規則。

□ 根ENTITY具有全域性標識。邊界內的ENTITY具有本地標識,這些標識只在AGGREGATE內部才是唯一的。

□ AGGREGATE外部的物件不能引用除根ENTITY之外的任何內部物件。根ENTITY可以把對內部ENTITY的引用傳遞給它們,但這些物件只能臨時使用這些引用,而不能保持引用。根可以把一個VALUE OBJECT的副本傳遞給另外一個物件,而不必關心它發生什麼變化,因為它只是一個VALUE,不再與AGGREGATE有任何關聯。

□ 作為上一條規則的推論,只有AGGREGATE的根才能直接通過資料庫查詢獲取。所有其他物件必須通過遍歷關聯來發現。

□ AGGREGATE內部的物件可以保持對其他AGGREGATE根的引用。

□ 刪除操作必須一次刪除AGGREGATE邊界之內的所有物件。(利用垃圾回收機制,這很容易做到。由於除了根以外的其他物件都沒有外部引用,因此刪除了根以後,其他物件均會被回收。)

□ 當提交對AGGREGATE邊界內部的任何物件的修改時,整個AGGREGATE的所有固定規則都必須滿足。


本地標識與全域性標識及物件引用

我們應該將ENTITY和VALUE OBJECT分門類別地聚集到AGGREGATE中,並定義每個AGGREGATE的邊界。在每個AGGREGATE中,選擇一個ENTITY作為根,並通過根來控制對邊界內其他物件的所有訪問。只允許外部物件保持對根物件的引用。對內部成員的臨時引用可以被傳遞出去,但僅在一次操作中有效。由於根控制訪問,因此不能繞過它來修改內部物件。這種設計有利於確保AGGREAGATE中的物件滿足所有固定規則,也可以確保在任何狀態變化時AGGREGATE作為一個整體滿足固定規則。

 

模式:FACTORY


與FACTORY的基本互動

當建立一個物件或建立整個AGGREGATE時,如果建立工作很複雜,或者暴露了過多的內部結果,則可以使用FACTORY進行封裝。

物件的建立本身可以是一個主要操作,但被建立的物件並不適合承擔複雜的裝配操作。將這些職責混在一起可能產生難以理解的拙劣設計。讓客戶直接負責建立物件又會使客戶的設計陷入混亂,並且破壞被裝配物件或AGGREGATE的封裝,而且導致客戶與被建立物件的實現之間產生過於緊密的耦合。

因此:應該講建立複雜物件的例項和AGGREGATE的職責轉移給單獨的物件,這個物件本身可能沒有承擔領域模型中的職責,但它仍是領域設計的一部分。提供一個封裝所有複雜裝配操作的介面,而且這個介面不需要客戶引用要被例項化的物件的具體類。在建立AGGREGATE時要把它作為一個整體,並確保它滿足固定規則。

 

模式:REPOSITORY


REPOSITORY為客戶執行一個搜尋

在所有持久化物件中,有一小部分必須通過基於物件屬性的搜尋來全域性訪問。當很難通過遍歷方式來訪問某些AGGREGATE根的時候,就需要使用這種訪問方式。它們通常是ENTITY,有時是具有複雜內部結構的VALUE OBJECT,還可能是列舉VALUE。而其他物件則不宜使用這種方式,因為這會混淆它們之間的重要區別。隨意的資料庫查詢會破壞領域物件的封裝和AGGREGATE。技術基礎設施和資料庫訪問機制的暴露會增加客戶的複雜度,並妨礙模型驅動的設計。

REPOSITORY是一個簡單的概念框架,它可用來封裝這些解決方案,並將我們的注意力重新拉回到模型上。REPOSITORY將某種型別的所有物件表示為一個概念集合(通常是模擬的)。它的行為類似於集合(collection),只是具有更復雜的查詢功能。

因此:為每種需要全域性訪問的物件型別建立一個物件,這個物件相當於該型別的所有物件在記憶體中的一個集合的“替身”。通過一個眾所周知的全域性介面來提供訪問。提供新增和刪除物件的方法,用這些方法來封裝在資料儲存中實際插入或刪除資料的操作。提供根據具體條件來挑選物件的方法,並返回屬性值滿足查詢條件的物件或物件集合(所返回的物件是完全例項化的),從而將實際的儲存和查詢技術封裝起來。只為那些確實需要直接訪問的AGGREGATE根提供REPOSITORY。讓客戶始終聚焦於模型,而將所有物件的儲存和訪問操作交給REPOSITORY來完成。

FACTORY與REPOSITORY的關係是:FACTORY負責處理物件生命週期的開始,而REPOSITORY幫助管理生命週期的中間和結束。從領域驅動設計的角度來看,FACTORY和REPOSITORY具有完全不同的職責。FACTORY負責製造新物件,而REPOSITORY負責查詢已有物件。

 

【學習心得】:有時候學習上的困難不是因為自己的理解能力差,而是缺乏一定的基礎溝通語言。急於求成和半路出家的問題就在於基礎的不紮實,也就是我們所說的野路子。我曾經也會認為用到了再來學,這都是技術圈子的一個悖論。就好像等自己需要用錢了再來理財一樣可笑。

 

第7章:使用語言:一個擴充套件的示例

 

【 第三部分 】通過重構來加深理解


第8章:突破


突破價值曲線

 

一般來說,持續重構讓事物逐步變得有序。程式碼和模型的每一次精化都讓開發人員有了更加清晰的認識。這使得理解上的突破成為可能。之後,一系列快速的改變得到了更符合使用者需要並更加切合實際的模型。其功能性及說明性急速增強,而複雜性卻隨之消失。這種突破不是某種技巧,而是一個事件。它的困難之處在於你需要判斷髮生了什麼,然後再決定如何處理。

當突破帶來更深層次的模型時,通常會令人感到不安。與大部分重構相比,這種變化的回報更多,風險也更高。而且突破出現的時機可能很不合時宜。儘管我們希望進展順利,但往往事與願違。過渡到真正的深層次模型需要從根本上調整思路,並且對設計做大幅修改。在很多專案中,建模和設計工作最重要的進展都來自於突破。

不要試圖去製造突破,那隻會使專案陷入困境。通常,只有在實現了許多適度的重構後才有可能出現突破。在大部分時間裡,我們都在進行微小的改進,而在這種連續的改進中模型深層含義也會逐漸顯現。

要為突破做好準備,應專注於知識消化過程,同時也要逐漸建立健壯的UBIQUITOUS LANGUAGE。尋找那些重要的領域概念,並在模型中清晰地表達出來。精化模型,使其更具有柔性。提煉模型。利用這些更容易掌握的手段使模型變得更清晰,這通常會帶來突破。

不要猶豫著不去做小的改進,這些改進即使脫離不開常規的概念框架,也可以逐漸加深我們對模型的理解。不要因為好高騖遠而使專案陷入困境。只要隨時注意可能出現的機會就夠了。

 

【學習心得】:我自己手頭上目前持有一個運行了近十年的千萬級使用者系統,至今還在持續運營和追加功能。對於以上那些突破的感受,我太有體會了。整個專案總共經歷了兩次大的突破,以及無數次小突破。而每一次突破都十分痛苦,但快樂著。離不開團隊的堅持,離不開團隊的持續學習,更離不開團隊吃苦精神。

 

第9章:將隱式概念轉變為顯示概念

深層建模聽起來很不錯,但是我們要如何時間它呢?深層模型之所以強大是因為它包含了領域的核心概念和抽象,能夠以簡單靈活的方式表達出基本的使用者活動、問題以及解決方案。

若開發人員識別出設計中隱含的某個概念或者在討論中收到啟發而發現一個概念時,就會對領域建模和響應的程式碼進行許多轉換,在模型中加入一個或多個物件或關係,從而將此概念顯示地表達出來。有時,這種從隱式概念到顯示概念的轉換可能就是一次突破。

概念挖掘

1、傾聽領域專家使用的語言。

有沒有一些術語能夠簡潔地表達出複雜的概念?他們有沒有糾正過你的用詞(也許是很委婉的提醒)?當你使用某個特定詞語時,他們臉上是否已經不再流露出迷惑的表情?這些都是暗示了某個概念也許可以改進模型。

2、檢查不足之處。

你所需要的概念並不總是浮在表面上,也絕不僅僅是通過對話和文件就能讓它顯現出來。有些概念可能需要你自己去挖掘和創造。要挖掘的地方就是設計中最不足的地方,也就是操作複雜且難於理解的地方。每當有新需求時,似乎都會讓這個地方變得更加複雜。有時,你很難意識到模型中丟失了什麼概念。也許你的物件能夠實現所有的功能,但是有些職責的實現卻很笨拙。而有時,你雖然能夠意識到模型中丟失了某些東西,但是卻無法找到解決方案。

3、思考矛盾之處。

由於經驗和需求的不同,不同的領域專家對同樣的事情會有不同的看法。即使是同一個人提供的資訊,仔細分析後也會發現邏輯上不一致的地方。在挖掘程式需求的時候,我們會不斷遇到這種令人煩惱的矛盾,但它們也為深層模型的實現提供了重要線索。有時矛盾只是術語說法上的不一致,有些則是由於誤解而產生的。但還有一種情況是專家們會給出相互矛盾的兩種說法。

4、查閱書籍。

在尋找模型概念時,不要忽略一些顯而易見的資源。在很多領域中,你都可以找到解釋基本概念和傳統思想的書籍。你依然需要與領域專家合作,提煉與你的問題相關的那部分知識,然後將其轉化為適用於面向物件軟體的概念。但是,查閱書籍也許能夠使你一開始就形成一致且深層的認識。

5、嘗試,再嘗試。

並不是所有這些方向性的改變都毫無用處。每次改變都會把開發人員更深刻的理解新增到模型中。每次重構都使設計變得更靈活且為那些可能需要修改的地方做好準備。我們其實別無選擇。只有不斷嘗試才能瞭解什麼有效什麼無效。企圖避免設計上的失誤將會導致開發出來的產品質量劣質,因為沒有更多的經驗可用來借鑑,同時也會比進行一系列快速實驗更加費時。

如何為那些不太明顯的概念建模?

1、顯示的約束。

約束是模型概念中非常重要的類別。它們通常是隱含的,將它們顯式地表現出來可以極大地提高設計質量。


(例子)為顯示錶達超訂策略而重構的模型  

2、將過程建模為領域物件。

首先要說明的是,我們都不希望過程變成模型的主要部分。物件是用來封裝過程的,這樣我們只需要考慮物件的業務目的或意圖就可以了。就像我們以上用來安排貨運路線的運輸系統例子,安排路線的過程具有業務意義。SERVICE是顯示錶達這種過程的一種方式,同時它還會降異常複雜的演算法封裝起來。

如果過程的執行有多種方式,那麼我們也可以用另一種方法來處理它,那就是將演算法本身或其中的關鍵部分放到一個單獨的物件中。這樣,選擇不同的過程就變成了選擇不同的物件,每個物件都表示一種不同的STRATEGY(策略)。

那麼,過程是應該被顯示錶達出來,還是應該被隱藏起來呢?區分的方法很簡單:它是經常被領域專家提起呢,還是僅僅被當作計算機程式機制的一部分?

約束和過程是兩大類概念模型,當我們用面嚮物件語言程式設計時,不會立即想到它們,然而它們一旦被我們視為模型元素,就真的可以讓我們的設計更為清晰。

 

3、模式:SPECIFICATION


檢查發票“謂詞”提取例子

業務規則通常不適合作為ENTITY和VALUE OBJECT的職責,而且規則的變化和組合也會被掩蓋領域物件的基本含義。但是將規則移出領域層的結果會更糟糕,因為這樣一來,領域程式碼就不再表達模型了。

邏輯程式設計提供了一個概念,即“謂詞”這種可分離、可組合的規則物件,但是要把這種概念用物件完全實現是很麻煩的。同時,這種概念過於通用,在表達設計意圖方面,它的針對性不如專門的設計那麼好。

因此:為特殊目的建立謂詞形式的顯式的VALUE OBJECT。SPECIFICATION就是一個謂詞,可用來確定物件是否滿足某些標準。

 

【學習心得】:專案的溝通成本之大,正是因為許多人的內心都會有一顆“自尊心”,我的領域我最懂,我的技術牛逼,你們業務人員可以一邊站。所以,想成為一個合格的團隊成員,至少得讓自己能成為一個合格的聆聽者。看似簡單,但我在生活中就發現了自己並非一位很好的聆聽者。錯過了許多對自己有用的資訊,更多還是用了許多自以為是的拍腦袋決策。

 

第10章:柔性設計


一些有助於獲得柔性設計的模式  

為了使專案能夠隨著開發工作的進行加速前進,而不會由於它自己的老化將停滯不前,設計必須要讓人們樂於使用,而且易於做出修改。這就是柔性設計(supple design)。

很多過度設計(overengineering)藉著靈活性的名義而得到合理的外衣。但是,過多的抽象層和間接設計常常成為專案的絆腳石。看一下真正為使用者帶來強大功能的軟體設計,你常常會發現一些簡單的東西。簡單並不容易做到。

 

模式:INTENTION-REVEALING INTERFACES(意圖揭示介面)

如果開發人員為了使用一個元件而必須要去研究它的實現,那麼就失去了封裝的價值。當某個人開發的物件或操作被別人使用時,如果使用這個元件的新的開發者不得不根據其實現來推測其用途,那麼他推測出來的可能並不是那個操作或類的主要用途。如果這不是那個元件的用途,雖然程式碼暫時可以工作,但設計的概念基礎已經被誤用了,兩位開發人員的意圖也是背道而馳。

因此:在命名類和操作時要描述它們的效果和目的,而不是表露它們是通過何種方式達到目的的。這樣可以使客戶開發人員不必去理解內部細節。這些名稱應該與UBIQUITOUS LANGUAGE保持一致,以便團隊成員可以迅速推斷出它們的意義。在建立一個行為之前先為它編寫一個測試,這樣可以促使你站在客戶開發人員的角度上來思考它。所有複雜的機制都應該封裝到抽象介面的後面,介面只表明意圖,而不表明方式。


新的方法名更能表達“油漆”有混合的作用  

模式:SIDE-EFFECT-FREE FUNCTION(無副作用功能)

大多數操作都會呼叫其他的操作,而後者又會呼叫另外一些操作。一旦形成這種任意深度的巢狀,就很難預測呼叫一個操作將要產生的所有後果。第二層和第三層操作的影響可能並不是客戶開發人員有意為之的,也是它們就變成了完全意義上的副作用(任何對未來操作產生影響的系統狀態改變都可以成為副作用)。

多個規則的相互作用或計算的組合產生的結果是很難預測的。開發人員在呼叫一個操作時,為了預測操作的結果,必須理解它的實現以及它所呼叫的其他方法的實現。如果開發人員不得不“揭開介面的面紗”,那麼介面的抽象作用就受到了限制。如果沒有了可以安全地預見到結果的抽象,開發人員就必須限制“組合爆炸”,這就限制了系統行為的豐富性。

因此:儘可能把程式的邏輯放到函式(返回結果而不產生副作用的操作稱為函式)中,因為函式是隻返回結果而不產生明顯副作用的操作。嚴格地把命令(引起明顯的狀態改變的方法)隔離到不返回領域資訊的、非常簡單的操作中。當發現了一個非常適合承擔複雜邏輯職責的概念時,就可以把這個複雜邏輯移到VALUE OBJECT中,這樣可以進一步控制副作用。


Paint分解了無副作用的Pigment Color類  

模式:ASSERTION(斷言)

把複雜的計算封裝到SIDE-EFFECT-FREE FUNCTION中可以簡化問題,但實體仍然會留有一些有副作用的命令,使用這些ENTITY的人必須瞭解使用這些命令的後果。

如果操作的副作用僅僅是由它們的實現隱式定義的,那麼在一個具有大量相互呼叫關係的系統中,起因和結果會變得一團糟。理解程式的唯一方式就是沿著分支路徑來跟蹤程式的執行,封裝完全失去了價值。跟蹤具體的執行也使抽象失去了意義。

因此:把操作的後置條件和類及AGGREGATE的固定規則表達清楚。如果在你的程式語言中不能直接編寫ASSERTION,那麼就把它們編寫成自動的單元測試。還可以把它們寫到文件或圖中(如果符合專案開發風格的話)。尋找在概念上內聚的模型,以便使開發人員更容易推斷出預期的ASSERTION,從而加快學習過程並避免程式碼矛盾。

INTENTION-REVEALING INTERFACE清楚地表明瞭用途,SIDE-EFFECT-FREE FUNCTION和ASSERTION使我們能夠更準確地預測結果,因此封裝和抽象更加安全。

 

模式:CONCEPTUAL CONTOUR(概念輪廓)

如果把模型或設計的所有元素都放在一個整體的大結構中,那麼它們的功能就會發生重複。外部介面無法給出客戶可能關心的全部資訊。由於不同的概念被混合在一起,它們的意義變得很難理解。

而另一方面,把類和方法分解開也可能是毫無意義的,這會使客戶更復雜,迫使客戶物件去理解各個細微部分是如何組合在一起的。更糟的是,有的概念可能會完全丟失。鈾原子的一半並不是鈾。而且,粒度的大小並不是唯一要考慮的問題,我們還要考慮粒度是在哪種場合下使用的。

因此:把設計元素(操作、介面、類和AGGREGATE)分解為內聚單元,在這個過程中,你對領域中一切重要劃分的直觀認識也要考慮在內。在連續的重構過程中觀察發生變化和保證穩定的規律性,並尋找能夠解釋這些變化模式的底層CONCEPTUAL CONTOUR。使模型與領域中那些一致的方面(正是這些方面使得領域成為一個有用的知識體系)相匹配。

我們的目標是得到一組可以在邏輯上組合起來的簡單介面,使我們可以用UBIQUITOUS LANGUAGE進行合理的表述,並且使那些無關的選項不會分散我的注意力,也不增加維護負擔。但這通常是通過重構才能得到的結果,很難在前期就實現。而且如果僅僅是從技術角度進行重構,可能永遠也不會出現這種結果;只有通過重構得到更深層次的理解,才能實現這樣的目標。

INTENTION-REVEALING INTERFACE使客戶能夠把物件表示為有意義的單元,而不僅僅是一些機制。SIDE-EFFECT-FREE FUNCTION和ASSERTION使我們可以安全地使用這些單元,並對它們進行復雜的組合。CONCEPTUAL CONTOUR的出現使模型的各個部分變得更加穩定,也使得這些單元更直觀,更易於使用和組合。

 

模式:STANDALONE CLASS(獨立的類)

即使是在MODULE內部,設計也會隨著依賴關係的增加而變得越來越難以理解。這加重了我們的思考負擔,從而限制了開發人員能處理的設計複雜度。隱式概念比顯式引用增加的負擔更大了。

低耦合是物件設計的一個基本要素。盡一切可能保持低耦合。把其他所有無關概念提取到物件之外。這樣類就變得完全獨立了,這就使得我們可以單獨地研究和理解它。每個這樣的獨立類都極大地減輕了因理解MODULE而帶來的負擔。

盡力把最複雜的計算提取到STANDALONE CLASS(獨立的類)中,實現此目的的一種方法是從存在大量依賴的類中將VALUE OBJECT建模出來。低耦合是減少概念過載的最基本方法。獨立的類是低耦合的極致。

 

模式:CLOSURE OF OPERATION(閉合操作)

        兩個實數相乘,結果仍為實數(實數是所有有理數和所有無理數的集合)。由於這一點永遠成立,因此我們說實數的“乘法運算是閉合的”:乘法運算的結果永遠無法脫離實數這個集合。當我們對集合中的任意兩個元素組合時,結果仍在這個集合中,這就叫做閉合操作。

                         ——The Math Forum,Drexel University

加法運算是實數集中的閉合運算。數學家們都極力避免去引入無關的概念,而閉合運算的性質正好為他們提供了這樣一種方式。

因此:在適當情況下,在定義操作時讓它的返回型別與其引數的型別相同。如果實現者(implementer)的狀態在計算中會被用到,那麼實現者實際上就是操作一個引數,因此引數和返回值應該與實現者有相同的型別。這樣的操作就是在該型別的例項集合中的閉合操作。閉合操作提供了一個高層介面,同時又不會引人對其他概念的任何依賴。

 

【學習心得】:經歷了(還在經歷)一個近十年的專案,我想自己還是比較有資格談談柔性設計的感受。沒有學習這些柔性概念之前,我們能持續高效並執行開發一個專案那麼長時間,功勞歸於一個重要的原則:簡單。起初,整個團隊都缺乏以上這些實用的模式理論作為參考,但大家都有秉承著一個“簡單”的共同原則,其實不知不覺中摸著石頭過河,在無數次重構中逐漸跟以上模式契合起來。當然,如果我們能提前認識這些基礎的理論基礎知識,我想不必要的彎路會少走許多。也當然,系統還在不斷完善中,現在認識也不晚。

 

第11章:應用分析模式

在《分析模式》一書中,Martin Fowler這樣定義分析模式:

分析模式是一種概念集合,用來表示業務建模中的常見結構。它可能只與一個領域有關,也可能跨多個領域。

Fowler所提出的分析模式來自於實踐經驗,因此只要用在合適的情形下,它們會非常實用。對於那些面對著具有挑戰性領域的人們,這些模型為他們的迭代開發過程提供了一個非常有價值的起點。“分析模式”這個名字本身就強調了其概念本質。分析模式不是技術方案,他們只是參考,用來指導人們設計特定領域中的模型。

分析模式最大的作用是借鑑其他專案的經驗,把那些專案中有關設計方向和實現結構的廣泛討論與當前模型的理解結合起來。脫離具體的上下文來討論模型思想不但難以落地,而且還會造成分析與設計嚴重脫節的風險,而這一點正是MODEL-DRIVEN DESIGN堅決反對的。

當你可以幸運地使用一種分析模式時,它一般並不會直接滿足你的需求。但它為你的研究提供了有價值的線索,而且提供了明確抽象的詞彙。它還可以知道我們的實現,從而省去很多麻煩。

我們應該把所有分析模式的知識融入知識消化和重構的過程中,從而形成更深刻的理解,並促進開發。當我們應用一種分析模式時,所得到的結果通常與該模式的文獻中記載的形式非常想像,只是因具體情況不同而略有差異。但有時完全看不出這個結果與分析模式本身有關,然而這個結果仍然是受該模式思想的啟發而得到的。

但有一個誤區是應該避免的。當使用眾所周知的分析模式中的術語時,一定要注意,不管其表面形式的變化有多大,都不要改變它所表示的基本概念。這樣做有兩個原因,一是模式中蘊含的基本概念將幫助我們避免問題,二是(也是更重要的原因)使用被廣泛理解或至少是被明確理解的術語可以增強UBIQUITOUS LANGUAGE。如果在模型的自然演變過程中模型的定義也發生改變,那麼就要修改模型名稱了。

 

【學習心得】:本章節主要還是藉助《分析模式》一書中的例子,用實踐例子來分析系統是如何在演繹過程使用模型的。這種科學謹慎的做法,才是一個工程師的基本觀念要求。

 

第12章:將設計模式應用於模型

在《設計模式》中,有些(但並非所有)模式可用作領域模式,但在這樣使用的時候,需要變換一下重點。有些模式反映了一些在領域中出現的深層概念。這些模式都有很大的利用價值。為了在領域驅動設計中充分利用這些模式,我們必須同時從兩個角度看待它們:從程式碼的角度來看它們是技術設計模式,從模型的角度來看它們就是概念模式。

模式:STRATEGY(也稱POLICY)


策略模式

策略模式:定義了一組演算法,將每個演算法封裝起來,並使它們可以互換。STRATEGY允許演算法獨立於使用它的客戶而變化[Gamma et al. 1995]

領域模型包含一些並非用於解決技術問題的過程,將它們包含進來是因為它們處理問題領域具有實際的價值。當必須從多個過程中進行選擇時,選擇的複雜性再加上多個過程本身的複雜性使局面失去控制。

因此:我們需要把過程中的易變部分提取到模型的一個單獨的“策略”物件中。將規則與它所控制的行為區分開。按照STRATEGY設計模式來實現規則或可替換的過程。策略物件的多個版本表示了完成過程的不同方式。

 

模式:COMPOSITE


組合模式

組合模式:將物件組織為樹來表示部分—整體的層次結構。利用COMPOSITE,客戶可以對單獨的物件和物件組合進行同樣的處理。[Gamma et al.1995]

當巢狀容器的關聯性沒有在模型中反映出來時,公共行為必然會在層次結構的每一層重複出現,而且巢狀也變得僵化(例如,容器通常不能包含同一層中的其他容器,而且巢狀的層數也是固定的)。客戶必須通過不同的介面來處理層次結構中的不同層,儘管這些層在概念上可能沒有區別。通過層次結構來遞迴地收集資訊也變得非常複雜。

因此:定義一個把COMPOSITE的所有成員都包含在內的抽象型別。在容器上實現那些查詢資訊的方法時,這些方法返回由容器內容所彙總的資訊。而“葉”節點則基於它們自己的值來實現這些方法。客戶只需使用抽象型別,而無需區分“葉”和容器。

 

【學習心得】:很多時候,技術人員釘子思維是無法區分技術角度和模型角度。雖然許多方法是相通的,但不通維度的思考方式也會產生巨大的效果。學以致用,不是停留在嘴巴上,是在實踐中證明的。

 

第13章:通過重構得到更深層次的理解

通過重構得到更深層次的理解是一個涉及很多方面的過程。我們有必要暫停一下,把一些要點歸納到一起。有三件事情是必須要關注的:

(1)以領域為本;

(2)用一種不同的方式來看待事物;

(3)始終堅持與領域專家對話。

在尋求理解領域的過程中,可以發現更廣泛的重構機會。但一提到傳統意義上的重構,我們頭腦中就會出現這樣一幅場景:一兩位開發人員坐在鍵盤前面,發現一些程式碼可以改進,然後立刻動手修改程式碼(當然還要用單元測試來驗證結果)。這個過程應該是一直進行下去,但它並不是重構過程的全部。

1、開始重構

與傳統重構觀點不同的是,即使在程式碼看上去很整潔的時候也可能需要重構,原因是模型的語言沒有與領域專家保持一致,或者新需求不能被自然地新增到模型中。重構的原因也可能來自學習:當開發人員通過學習獲得了更深刻的理解,從而發現了一個得到更清晰或更有用的模型的機會。

2、探索團隊

不管問題的根源是什麼,下一步都是要找到一種能夠使模型表達變得更清楚和更自然的改進方案。這可能只需要做一些簡單、明顯的修改,只需幾小時即可完成。在這種情況下,所做的修改類似於傳統重構。但尋找新模型可能需要更多時間,而且需要更多人蔘與。

修改的發起者會挑選幾位開發人員一起工作,這些開發人員應該擅長思考該類問題,瞭解領域,或者掌握深厚的建模技巧。如果涉及一些難以捉摸的問題,他們還要請一位領域專家加入。想要保證重構迭代過程的效率,需要注意幾個關鍵事項:自主決定,注意範圍和休息,以及練習使用UBIQUITOUS LANGUAGE。

3、借鑑先前的經驗

我們沒有必要總去做一些無謂的重複工作。用於查詢缺失概念或改進模型的頭腦風暴過程具有巨大的作用,通過這個過程可以收集來自各個方面的想法,並把這些想法與已有知識結合起來。隨著知識消化的不斷開展,就能找到當前問題的答案。

4、針對開發人員的設計

軟體不僅僅是為使用者提供的,也是為開發人員提供的。開發人員必須把他們編寫的程式碼與系統的其他部分整合到一起。在迭代過程中,開發人員反覆修改程式碼。開發人員應該通過重構得到更深層的理解,這樣既能夠實現柔性設計,也能夠從這樣一個設計中獲益。

5、重構的時機

如果一直等到完全證明了修改的合理性之後才去修改,那麼可能要等待太長時間了。專案正承受巨大的耗支,推遲修改將使修改變得更難執行,因為要修改的程式碼已經變得更加複雜,並更深地嵌入到其他程式碼中。持續重構漸漸被認為是一種“最佳實踐”,但大不部分團隊仍然對它抱有很大的戒心。

在探索領域的過程中,在培訓開發人員的過程中,以及在開發人員與領域專家進行思想交流的過程中,必須始終堅持把“通過重構得到更深層次理解”作為這些工作的一部分。因此,當發生一下情況時,就應該進行重構了:

□ 設計沒有表達出團隊對領域的最新理解;

□ 重要的概念被隱藏在設計中了(而且你已經發現了把它們呈現出來的方法);

□ 發現了一個能令某個重要的設計部分變得更靈活的機會。

6、危機就是機遇

傳統意義上的重構聽起來是一個非常穩定的過程。但通過重構得到更深層理解往往不是這樣的。在對模型進行一段時間穩定的改進後,你可能突然有所頓悟,而這會改變模型中的一切。這些突破不會每天都發生,然而很大一部分深層模型和柔性設計都來自這些突破。

這樣的情況往往看起來不像是機遇,而更像危機。例如,你突然發現模型中一些明顯的缺陷,在表達方面顯示出一個很大的漏洞,或存在一些沒有表達清楚的關鍵區域。或者有些描述是完全錯誤的。這些都表明團隊對模型的理解已經達到了一個新的水平。他們現在站在更高的層次上發現了原有模型的弱點。他們可以從這種角度構思一個更好的模型。

 

【學習心得】:我曾幾何時一直認為,發現自己問題是一種恥辱。這種思維極其可怕,當我不再發現自己問題的時候,那才叫可怕。在軟體領域,新思維的提升叫重構,在生活方面,新觀念的形成叫重生。

 

【第四部分】戰略設計


第14章:保持模型的完整性

模型最基本的要求是它應該保持內部一致,術語總具有相同的意義,並且不包含相互矛盾的規則:雖然我們很少明確地考慮這些要求。模型的內部一致性又叫統一(unification),這種情況下,每個術語都不會有模稜兩可的意義,也不會有規則衝突。除非模型在邏輯上是一致的,否則它就沒有意義。在理想世界中,我們可以得到涵蓋整個企業領域的單一模型。這個模型將是統一的,沒有任何相互矛盾或相互重疊的術語定義。每個有關領域的邏輯宣告都是一致的。

但,大型系統開發並非如此理想。在整個企業系統中保持這種水平的統一是一件得不償失的事情。在系統的各個不同部分中開發多個模型是很有必要的,但我們必須慎重地選擇系統的哪些部分可以分開,以及它們之間是什麼關係。我們需要用一些方法保持模型關鍵部分的高度統一。所有這些都不會自行發生,而且光有良好的意願也沒用的。它只有通過有意識的設計決策和建立特定過程才能實現。大型系統領域模型的完全統一既不可行,也不划算。

 

模式:BOUNDED CONTEXT(邊界上下文)

細胞之所以能夠存在,是因為細胞膜限定了什麼在細胞內,什麼在細胞外,並且確定了什麼物質可以通過細胞膜。

任何大型專案都會存在多個模型。而當基於不同模型的程式碼被組合到一起後,軟體就會出現bug,變得不可靠和難以理解。團隊成員之間的溝通變得混亂。人們往往弄不清楚一個模型不應該在哪個上下文中使用。

因此:明確地定義模型所應用的上下文。根據團隊的組織,軟體系統的各個部分的用法以及物理表現(程式碼和資料庫模式等)來設定模型的邊界。在這些邊界中嚴格保持模型的一致性,而不要收到邊界之外問題的干擾和混淆。

但記住,BUOUNDED CONTEXT不是MODULE。有時這兩個概念易引起混淆,但它們是具有不同動機的不同模式。確實,當兩組物件組成兩個不同模型時,人們幾乎總是把它們放在不同的MODULE中。這樣做的確提供了不同的名稱空間(對不同的CONTEXT很重要)和一些劃分方法。但人們也會在一個模型中用MODULE來組織元素,它們不一定要表達劃分CONTEXT的意圖。MODULE在BOUNDED CONTEXT內部建立的獨立名稱空間實際上使人們很難發現意外產生的模型分裂。

我們通過定義這個BOUNDED CONTEXT,最終得到了什麼?對CONTEXT內的團隊而言:清晰!對於CONTEXT之外的團隊而言:自由。當然,邊界只不過是一些特殊的位置。各個BUONDED CONTEXT之間的關係需要我們仔細地處理。CONTEXTMAP(上下文圖)畫出了上下文範圍,並給出了CONTEXT以及它們之間聯絡的總體檢視,而幾種模式定義了CONTEXT之間的各種關係的性質。CONTINUOUS INTEGRATION(持續整合)的過程可以使模型在BOUNDED CONTEXT中保持一致。

如何識別BOUNDED CONTEXT中的不一致?很多徵兆都可能表明模型出現了差異。最明顯的是已編碼的介面不匹配。對於更微妙的情況,一些意外行為也可能是一種訊號。採用了自動測試的CONTINUOUS INTEGRATION可以幫助捕捉到這類問題,但語言上的混亂往往是一種早期訊號。

將不同模型的元素組合到一起可能會引發兩類問題:重複的概念和假同源。重複的概念是指兩個模型元素(以及伴隨的實現)實際上表示同一個概念,而假同源是指使用相同術語(或已實現的物件)的兩個人認為他們是在談論同一件事情,但實際上並不是這樣。假同源可能稍微少見一點,但它潛在的危害更大。

 

模式:CONTINUOUS INTEGRATION(持續整合)

當很多人在同一個BOUNDED CONTEXT中工作時,模型很容易發生分裂。團隊越大,問題就越大,但即使3、4個人的團隊也有可能會遇到嚴重的問題。然而,如果將系統分解為更小的CONTEXT,最終又難以保持整合度和一致性。

因此:建立一個把所有程式碼和其他實現工件頻繁地合併到一起的過程,並通過自動化測試來快速查明模型的分類問題。嚴格堅持使用UBIQUITOUS LANGUAGE,以便在不同人的頭腦中演變出不同的概念時,是所有人對模型都能達成一個共識。

 

模式:CONTEXT MAP(上下文整體關聯圖)

其他團隊中的人員並不是十分清楚CONTEXT的邊界,他們會不知不覺地做出一些更改,從而使邊界變得模糊或者使互連變得複雜。當不同的上下文必須互相連線時,它們可能會互相重疊。

因此:識別在專案中起作用的每個模型,並定義其BOUNDED CONTEXT。這包括非面向物件子系統的隱含模型。為每個BOUNDED CONTEXT命名,並把名稱新增到UBIQUITOUS LANGUAGE中。描述模型之間的聯絡點,明確所有通訊需要的轉換,並突出任何共享的內容。先將當前的情況描繪出來,以後再做改變。

CONTEXT MAP無需拘泥於任何特定的文件格式。我發現類似本章的簡圖在視覺化和溝通上下文圖方面很有幫助。有些人可能喜歡使用較多的文字描述或別的圖形表示。在某些情況下,團隊成員之間的討論就足夠了。需求不同,細節層次也不同。不管CONTEXT MAP採用什麼形式,它必須在所有專案人員之間共享,並被他們理解。它必須為每個BOUNDED CONTEXT提供一個明確的名稱,而且必須闡明聯絡點和它們的本質。

下面介紹的這些模式涵蓋了將兩個模型關聯起來的眾多策略。這些模式的主要區別包括你對另一個模型的控制程度、兩個團隊之前合作水平和合作型別,以及特性和資料的整合程度。

 

模式:SHARED KERNEL(共享核心)

當不同團隊開發一些緊密相關的應用程式時,如果團隊之間不進行協調,即使短時間內能夠獲得快速進展,但他們開發出的產品可能無法結合到一起。租後可能不得不耗費大量精力在轉換層上,並且頻繁地進行改動,不如一開始就用CONTINUOUS INTEGRATION那麼省心省力,同時這也造成重複工作,並且無法實現公共的UBIQUITOUS LANGUAGE所帶來的好處。

因此:從模型中選出兩個團隊都同意共享的一個子集。當然,除了這個模型子集以外,還包括與該模型部分相關的程式碼子集,或資料庫設計的子集。這部分明確共享的內容具有特殊的地位,一個團隊在沒與另一個團隊商量的情況下不應擅自更改它。功能系統要經常進行整合,但整合的頻率應該比團隊中CONTINUOUS INTEGRATION的頻率低一些。在進行這些整合的時候,兩個團隊都要執行測試。

 

模式:CUSTOMER/SUPPLIER DEVELOPMENT TEAM(客戶/供應商 開發團隊)

我們經常會碰到這樣的情況:一個子系統主要服務於另外一個子系統;“下游”元件執行分析或其他功能,這些功能向“上游”元件反饋的資訊非常少,所有依賴都是單向的。兩個子系統通常服務於完全不同的使用者群,其執行的任務也不同,在這種情況下使用不同的模型會很有幫助。

如果下游團隊對變更具有否決權,或請求變更的程式太複雜,那麼上游團隊的開發自由度就會受到限制。由於擔心破壞下游系統,上游團隊甚至會受到抑制。同時,由於上游團隊掌握優先權。下游團隊有時也會無能為力。

因此:在兩個團隊之間建立一種明確的客戶/供應商關係。在計劃會議中,下游團隊相當於上游團隊的客戶。根據下游團隊的需求來協商需要執行的任務併為這些任務做預算,以便每個人都知道雙方的約定和進度。兩個團隊共同開發自動化驗收測試,用來驗證預期的介面。把這些測試新增到上游團隊的測試套件中,以便作為其持續整合的一部分來執行。這些測試使上游團隊在做出修改時不必擔心對下游團隊產生副作用。

 

模式:CONFORMIST(承諾)

當兩個具有上游/下游關係的團隊不歸同一個管理者指揮時,CUSTOMER/SUPPLIER TEAM這樣的合作模式就不會湊效。勉強應用這種模式會給下游團隊帶來麻煩。

當兩個開發團隊具有上/下游關係時,如果上游團隊沒有動力來滿足下游團隊的需求,那麼下游團隊將無能為力。出於利他主義的考慮,上游開發人員可能會做出承諾,但他們可能不會履行承諾。下游團隊出於良好的意願會相信這些承諾,從而根據一些永遠不會實現的特性來制定計劃。下游專案只能被擱置,直到團隊最終學會利用現有條件自力更生為止。下游團隊不會得到根據他們的需求而量身定做的介面。

因此:通過嚴格遵從上游團隊的模型,可以消除在BOUNDED CONTEXT之間進行轉換的複雜性。儘管這會限制下游設計人員的風格,而且可能不會得到理想的應用程式模型,但選擇CONFORMITY模式可以極大地簡化整合。此外,這樣還可以與供應商團隊共享UBIQUITOUS LANGUAGE。供應商處於統治地位,因此最好使溝通變容易。他們從利他主義的角度出發,會與你分享資訊。

SHARED KERNEL是兩個高度協調的團隊之間的合作模式,而CONFORMIST模式則是應對一個對合作不感興趣的團隊進行整合。

 

模式:ANTICORRUPTION LAYER(隔離層)

新系統幾乎總是需要與遺留系統或其他系統進行整合,這些系統具有自己的模型。當把參與整合的BOUNDED CONTEXT設計完善並且團隊相互合作時,轉換層可能很簡單,甚至很優雅。但是,當邊界那側發生滲透時,轉換層就要承擔起更多的防護職責。

當正在構建的新系統與另一個系統的介面很大時,為了克服連線兩個模型而帶來的困難,新模型所表達的意圖可能會被完全改變,最終導致它被修改得像另一個系統的模型了(以一種特定風格)。遺留系統的模型通常很弱。即使對於那些模型開發得很好的例外情況,它們可能也不符合當前專案的需要。然而,整合遺留系統仍然具有很大的價值,而且有時還是絕對必要的。

因此:建立一個隔離層,以便根據客戶自己的領域模型來為客戶提供相關功能。這個層通過另一個系統現有介面與其進行對話,而只需對那個系統作出很少的修改,甚至無需修改。在內部,這個層在兩個模型之間進行必要的雙向轉換。

這種連線兩個系統的機制可能會使我們想到把資料從一個程式傳輸到另一個程式,或者從一個伺服器傳輸到另一個伺服器。我們很快就會討論技術通訊機制的使用。但這些細節問題不應與ANTICORRUPTION LAYER混淆,因為ANTICORRUPTION LAYER並不是向另一個系統傳送訊息的機制。想反,它是不同的模型和協議之間轉換概念物件和操作的機制。

如何設計ANTICORRUPTION LAYER的介面?

ANTICORRUPTION LAYER的公共介面通常以一組SERVICE形式出現,但偶爾也會採用ENTITY的形式。在我們的模型中,把外部系統表示為一個單獨元件可能是沒有意義的。最好是使用多個SERVICE(或偶爾使用ENTITY),其中每個SERVICE都使用我們的模型來履行一致的職責。

如何實現ANTICORRUPTION LAYER?

對ANTICORRUPTION LAYER設計進行組織的一種方法是把它實現為FACEDE、ADAPTER和轉換器的組合,外加兩個系統之間進行對話所需的通訊和傳輸機制。


ANTICORRUPTION LAYER的結構  

模式:SEPARATE WAY(各行其道)

我們必須嚴格劃定需求的範圍。如果兩組功能之間的關係並非必不可少,那麼二者完全可以彼此獨立。因為整合總是代價高昂,而有時獲益卻很小。

因此:宣告一個與其他上下文毫無關聯的BOUNDED CONTEXT,使開發人員能夠在這個小範圍內找到簡單、專用的解決方案。

採用SEPARATE WAY(各行其道)模式需要預先決定一些選項。儘管持續重構最後可以撤銷任何決策,但完全隔離開發的模型是很難合併的。如果最終仍需要整合,那麼轉換層將是必要的,而且可能很複雜。當然,不管怎樣,這都是我們將要面對的問題。現在,讓我們回到更為合作的關係上,來看一下幾種提高整合度的模式。

 

模式:OPEN HOST SERVICE(開放主機服務)

當一個子系統必須與大量其他系統進行整合時,為每個整合都定製一個轉換層可能會減慢團隊的工作速度。需要維護的東西會越來越多。而且進行修改的時候擔心的事情也會越來越多。

因此:定義一個協議,把你的子系統作為一組SERVICE供其他系統訪問。開放這個協議,以便所有需要與你的子系統整合的人都可以使用它。當有新的整合需求時,就增強並擴充套件這個協議,但個別團隊的特殊需求除外。滿足這種特殊需求的方法是使用一次性的轉換器來擴充協議,以便使共享協議簡單且內聚。

這種通訊形式暗含一些共享的模型詞彙,它們是SERVICE介面的基礎。這樣,其他子系統就變成了與OPEN HOST(開放主機)的模型相連線,而其他團隊則必須學習HOST團隊所使用的專用術語。在一些情況下,使用一個眾所周知的PUBLISHED LANGUAGE(公開發布的語言)作為交換模型可以減少耦合並簡化理解。

 

模式:PUBLISHED LANGUAGE(公開發布的語言)

與現有領域模型進行直接的轉換可能不是一種好的解決方案。這些模型可能過於複雜或設計得較差。它們可能沒有被很好地文件化。如果把其中一個模型作為資料互動語言,它實際上就被固定住了,而無法滿足新的開發需求。

因此:把一個良好文件化的、能夠表達出所需領域資訊的共享語言作為公共的通訊媒介,必要時在其他資訊與該語言之間進行轉換。

 

“大象”的統一

講一個盲人摸象的故事:

第一個盲人碰巧摸到了大象寬闊結實的身軀,就以為大象就像一堵牆;

······

第三個盲人碰巧把扭動著的象鼻抓在書中,就大膽認為大象就像一條蛇;

第四個盲人急切伸出雙手,摸到了大象的膝蓋,就很明顯地認為大象就像一顆樹;

······

第六個盲人一開始摸這頭大象,就抓住了它擺動著的尾巴,就認為大象就像一根繩子。

即便他們對大象的本質不能達成完成的一致,這些盲人仍然可以根據他們所觸控到的大象身體的部位來擴充套件各自的認識。如果並不需要整合,那麼模型統不統一就無關緊要。如果他們需要進行一些整合,那麼實際