方法論系列:一文說透領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)核心概念
基本概念
什么是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)?
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design,簡稱DDD),是一種指導(dǎo)軟件系統(tǒng)工程建設(shè)的方法論。
「通過抽象提取的手段,將復(fù)雜的領(lǐng)域業(yè)務(wù)細(xì)節(jié)抽象成精簡的業(yè)務(wù)模型及其實(shí)現(xiàn)的匹配。」
可以理解為:針對某個(gè)業(yè)務(wù)領(lǐng)域的,面向?qū)ο笏季S的回歸和升華。
在面對一個(gè)新領(lǐng)域時(shí),甄別哪些對象對系統(tǒng)有用?哪些對擬建系統(tǒng)無用?如何保證選取的模型對象恰好夠用——既不過度設(shè)計(jì)又能滿
足業(yè)務(wù)要求?
領(lǐng)域?qū)ο蟛⒉皇仟?dú)立存在的,對象之間有各種千絲萬縷的聯(lián)系(如地鐵站、線路和列車之間的關(guān)系),正是這種聯(lián)系造成了系統(tǒng)的復(fù)
雜度。
很多時(shí)候修改一處變更,則牽一發(fā)而動(dòng)全身,對象的封裝機(jī)制僅僅只能解決有限的部分問題。
通過DDD,則可以保留對象之間有用的關(guān)系而去掉無用的關(guān)系,限定變更影響范圍來降低系統(tǒng)的復(fù)雜度。
DDD要求系統(tǒng)架構(gòu)師、系統(tǒng)開發(fā)工程師等技術(shù)人員,與業(yè)務(wù)領(lǐng)域?qū)<疫_(dá)成業(yè)務(wù)上的共同認(rèn)知。
其基本方法則是站在業(yè)務(wù)角度,從頂層抽象,用上帝視角來看待整體業(yè)務(wù)。
DDD與傳統(tǒng)OO的區(qū)別在于,前者是業(yè)務(wù)視角,后者是技術(shù)視角;前者從整體出發(fā)建模后設(shè)計(jì),后者是各種細(xì)節(jié)設(shè)計(jì)后堆疊出一個(gè)系統(tǒng);
前者領(lǐng)域知識(shí)豐厚且可傳遞,后者將系統(tǒng)做成“四不像” ……
DDD要求技術(shù)人員將精力投入一部分到業(yè)務(wù)理解上,而不是單純的做技術(shù)工作。
DDD是一種較為抽象的設(shè)計(jì)指引,不會(huì)涉及具體某個(gè)業(yè)務(wù)領(lǐng)域的設(shè)計(jì)實(shí)踐,但國內(nèi)有參考案例,如很多體量較大的互聯(lián)網(wǎng)公司早就在實(shí)踐DDD,特別是做業(yè)務(wù)中臺(tái)的時(shí)候。
一般設(shè)計(jì)的時(shí)候需要考慮上下文環(huán)境,同時(shí)要跟業(yè)務(wù)領(lǐng)域?qū)<疑钊牒献鳎缡紫冉y(tǒng)一術(shù)語,明確需求邊界,識(shí)別關(guān)鍵對象,理清關(guān)鍵業(yè)務(wù)對象之間的關(guān)系等等。
DDD在Martin Flower等人的傳播下,目前已成為微服務(wù)架構(gòu)必不可少的指導(dǎo)思想。
DDD并不是萬能的
DDD是OO的一種升華,但同樣存在不足,如在安全、權(quán)限方面的考慮,與開發(fā)框架如何融合等問題。
因此, DDD與OOP, MDD(模型驅(qū)動(dòng)設(shè)計(jì))和MDA(模型驅(qū)動(dòng)架構(gòu))等理念并不是非此即彼的關(guān)系,而是要根據(jù)具體場景結(jié)合使用。
國內(nèi)外應(yīng)用DDD的一線公司及領(lǐng)域
公司:thoughtworks,阿里巴巴集團(tuán), 360集團(tuán)等。
應(yīng)用DDD的領(lǐng)域:金融、保險(xiǎn)、電商等應(yīng)用。
未應(yīng)用DDD的領(lǐng)域:區(qū)塊鏈、大數(shù)據(jù)相關(guān)、 AI相關(guān)、純技術(shù)中間件團(tuán)
隊(duì)……
先驅(qū):Eric Evans(概念提出者)、 Martin Flower(推廣者)。
構(gòu)建領(lǐng)域知識(shí)/模型
領(lǐng)域模型:與業(yè)務(wù)領(lǐng)域?qū)<医涣鳎瑢⒘闵⒌臉I(yè)務(wù)知識(shí)建立抽象——在腦海里建立一個(gè)業(yè)務(wù)藍(lán)圖,隨后不斷完善使之越來越清晰。
領(lǐng)域模型不需具備一本百科普及類似的結(jié)構(gòu),而是經(jīng)過嚴(yán)格組織并選擇性抽象后的知識(shí)——只選取對系統(tǒng)有用的領(lǐng)域知識(shí),忽略掉無關(guān)的部分。
領(lǐng)域模型貫穿設(shè)計(jì)和開發(fā)的全過程。如何取舍領(lǐng)域?qū)ο笫窃O(shè)計(jì)階段
的工作,如軟件會(huì)關(guān)注客戶的聯(lián)系方式,但不太會(huì)關(guān)心客戶眼睛的
顏色。
模型是最基礎(chǔ)部分,可以簡化復(fù)雜問題的設(shè)計(jì)過程。
模型是用來與領(lǐng)域?qū)<摇⒓軜?gòu)師、開發(fā)工程師和測試工程師進(jìn)行交流的核心工具,因此需要做到精確、完整、無二義性。
模型的表現(xiàn)方式可以是圖、用例、也可以是文檔,甚至是偽代碼。
模型也可以用來進(jìn)行代碼設(shè)計(jì)(非軟件設(shè)計(jì)中所說的架構(gòu)設(shè)計(jì),這里指具體的代碼細(xì)節(jié),如代碼分層,如何分包等)
構(gòu)建領(lǐng)域知識(shí)/模型——簡單案例分解
地鐵監(jiān)控系統(tǒng)(假想需求):不考慮完整的地鐵網(wǎng)路控制系統(tǒng),只跟蹤有換乘線路的單個(gè)地鐵站,判斷某一條線是否遵照了預(yù)定的線路,以及是否有可能發(fā)生碰撞。
領(lǐng)域?qū)<遥旱罔F航線管理或監(jiān)控專家,但不能指望他們完整的描述
這個(gè)領(lǐng)域的所有問題。他們可能會(huì)描述的內(nèi)容包括:列車線路號(hào)、進(jìn)站時(shí)間、出站時(shí)間、上一站、下一站等知識(shí)。而我們需要從這些看似雜亂的信息中找到規(guī)律。

由此就抽象了三個(gè)基本對象:列車,起始站和目的站(可以合并為同一個(gè)對象用狀態(tài)區(qū)分)。然后他們之間的關(guān)系又會(huì)引發(fā)出一個(gè)新的對象:路線。
于是我們進(jìn)一步整理(交流->抽象->反饋-進(jìn)一步交流):

到這一步,是通過列車和上一站與下一站總結(jié)出來的結(jié)論,但實(shí)際上一列列車從起點(diǎn)站到終點(diǎn)站之間,會(huì)組成一條完整的曲線,甚至還要區(qū)分內(nèi)圈與外圈等多條曲線。
簡單來說,這些曲線是由很多上一站與下一站組成的。

列車的形式并不是由司機(jī)決定的,假設(shè)都會(huì)有列車的出行計(jì)劃。則我們可以進(jìn)一步將模型整理成下面這樣(現(xiàn)實(shí)肯定復(fù)雜很多倍,這里僅是一個(gè)例子)

通用語言
業(yè)務(wù)人員滿嘴領(lǐng)域?qū)I(yè)詞匯(行話),開發(fā)人員滿腦子類、對象、設(shè)計(jì)模式、繼承、多態(tài)、抽象等,甚至兩類人的思維模型天差地別,交流起來就類似「雞同鴨講」 ,所謂隔行如隔山。
DDD核心原則之一:使用基于模型的語言。確保團(tuán)隊(duì)使用的語言在所
有的交流形式中保持一致,這種語言成為“通用語言(Ubiquitous Language)”
通用語言成型方式:介于領(lǐng)域術(shù)語和開發(fā)術(shù)語之間的,通過類比和舉例兩種方式達(dá)成理解上的一致后,形成名詞集合。(個(gè)人實(shí)操經(jīng)驗(yàn),僅供參考)
當(dāng)一個(gè)領(lǐng)域的通用語言成熟以后,是可以直接由開發(fā)人員轉(zhuǎn)化為代碼中對象的表示的,甚至單詞都可以不用變化。
如此一來,代碼的可讀性得到提高,模型也完美地呈現(xiàn)了。當(dāng)模型逐步變大時(shí),技術(shù)上的意外可以得到一定程度的控制。
小結(jié)
本部分結(jié)合一個(gè)簡單的例子,介紹了DDD相關(guān)的基本概念和部分方法。
這屬于理論層面的內(nèi)容,更多的屬于在項(xiàng)目前期需求交流和設(shè)計(jì)階段的工作。
接下來的部分將介紹如何依賴領(lǐng)域模型進(jìn)行落地的方法:將領(lǐng)域模型設(shè)計(jì)的內(nèi)容轉(zhuǎn)換為具體的代碼。
模型驅(qū)動(dòng)設(shè)計(jì)
模型驅(qū)動(dòng)設(shè)計(jì)——概述
理論總是完美的,落地是有困難的。技術(shù)人員如何經(jīng)受生產(chǎn)環(huán)境的考驗(yàn)?能做到易擴(kuò)展和易維護(hù)嗎?
領(lǐng)域可以從不同角度被表現(xiàn)為多種模型,但只能選擇容易被輕易和準(zhǔn)確轉(zhuǎn)換為代碼的模型。
推薦的做法是在模型設(shè)計(jì)階段就讓開發(fā)人員參與討論,在建模的過程中,同時(shí)考慮如何轉(zhuǎn)換為代碼的問題,形成良性的反饋機(jī)制——問題越早被識(shí)別出來,就越容易被解決。
模型驅(qū)動(dòng)設(shè)計(jì)的基本構(gòu)成要素
模型驅(qū)動(dòng)設(shè)計(jì)——分層架構(gòu)
不分層的代碼無論是閱讀還是維護(hù)都及其困難。
分層的原則與高內(nèi)聚低耦合的理念不謀而合。
一般分層分為UI、應(yīng)用、領(lǐng)域和基礎(chǔ)設(shè)施,其中基礎(chǔ)設(shè)施一般與業(yè)務(wù)無關(guān),屬于技術(shù)組件級(jí)別(中間件、開發(fā)框架等)。
在這其中,領(lǐng)域部分是本文重點(diǎn)研究的對象。
| 層 | 釋義 |
| 用戶界面/展現(xiàn)層 | 負(fù)責(zé)向用戶展現(xiàn)信息以及解釋用戶命令/自演示指引/操 作手冊…… |
| 應(yīng)用層 | 很薄的一層,用來協(xié)調(diào)應(yīng)用的活動(dòng)。 不包含業(yè)務(wù)邏輯。 不保留業(yè)務(wù)對象的狀態(tài),但保有應(yīng)用任務(wù)的進(jìn)度狀態(tài)。 |
| 領(lǐng)域?qū)? | 包含關(guān)于領(lǐng)域的信息。 是業(yè)務(wù)軟件的核心所在。 這里保留業(yè)務(wù)對象的狀態(tài),對業(yè)務(wù)對象和他們狀態(tài)的持 久化被委托給了基礎(chǔ)設(shè)施層。 |
| 基礎(chǔ)設(shè)施層 | 作為其它層的支撐庫存在。 提供了層間的通信,實(shí)現(xiàn)對業(yè)務(wù)對象的持久化,包含對 用戶界面層的支撐庫等左右。 包括DB、緩存、 MQ、開發(fā)框架等基礎(chǔ)組件…… |
模型驅(qū)動(dòng)設(shè)計(jì)——實(shí)體
實(shí)體是有標(biāo)識(shí)符的一種對象,在java中一般表現(xiàn)為POJO。標(biāo)識(shí)符一般可以描述為ID,如用戶編號(hào)、銀行卡編號(hào)等,對應(yīng)到數(shù)據(jù)庫中,一般表示業(yè)務(wù)主鍵,或者是有唯一索引的字段。
不到萬不得已,盡量不使用屬性去映射對象(常見的通過姓名和身份證號(hào)去映射客戶)。
1. 失血模型:模型僅僅包含數(shù)據(jù)的定義和getter/setter方法,業(yè)務(wù)邏輯和應(yīng)用邏輯都放到服務(wù)層中。這種類在Java中叫POJO,在.NET中叫POCO。
2. 貧血模型:貧血模型中包含了一些業(yè)務(wù)邏輯,但不包含依賴持久層的業(yè)務(wù)邏輯。這部分依賴于持久層的業(yè)務(wù)邏輯將會(huì)放到服務(wù)層中。可以看出,貧血模型中的領(lǐng)域?qū)ο笫遣灰蕾囉诔志脤拥摹?/span>
3. 充血模型:充血模型中包含了所有的業(yè)務(wù)邏輯,包括依賴于持久層的業(yè)務(wù)邏輯。所以,使用充血模型的領(lǐng)域?qū)邮且蕾囉诔志脤樱唵伪硎揪褪?UI層->服務(wù)層->領(lǐng)域?qū)?lt;->持久層。
4. 脹血模型:脹血模型就是把和業(yè)務(wù)邏輯不想關(guān)的其他應(yīng)用邏輯(如授權(quán)、事務(wù)等)都放到領(lǐng)域模型中。我感覺脹血模型反而是另外一種的失血模型,因?yàn)榉?wù)層消失了,領(lǐng)域?qū)痈闪朔?wù)層的事,到頭來還是什么都沒變。
模型驅(qū)動(dòng)設(shè)計(jì)——值對象
實(shí)體是可以被跟蹤的,但跟蹤和創(chuàng)建標(biāo)識(shí)符需要很大的成本,特別是涉及DB分片的系統(tǒng)。也可能帶來性能問題,因?yàn)樾枰獙γ總€(gè)對象產(chǎn)生一個(gè)實(shí)例。
當(dāng)只關(guān)心一個(gè)對象所擁有的屬性而不需要其標(biāo)識(shí)符的時(shí)候,可以使用另一類對象:值對象(Value Object,簡稱VO)。也有人稱之為數(shù)據(jù)傳輸對象(Data Transfer Object,簡稱DTO)。
值對象相對實(shí)體對象而言,是可以輕易創(chuàng)建和丟棄的,一般設(shè)計(jì)為不可變類。同時(shí)VO應(yīng)該盡量保持簡單。
值對象可以包含其他的值對象,甚至可以包含對實(shí)體對象的引用。值對象的命名一般是在實(shí)體對象名稱之后加上VO或DTO字樣以示區(qū)別,也可以單獨(dú)分包,但僅通過包名來區(qū)分的話,容易引起誤會(huì)。
VO一般通過構(gòu)造方法進(jìn)行初始化,也可以通過builder的方式進(jìn)行。
模型驅(qū)動(dòng)設(shè)計(jì)——服務(wù)
實(shí)體對象和值對象應(yīng)該盡量減少行為,僅僅包含屬性和對屬性進(jìn)行set或get的方法。
比如設(shè)計(jì)一個(gè)賬戶,當(dāng)涉及轉(zhuǎn)賬操作時(shí),無論是將這個(gè)動(dòng)作放在轉(zhuǎn)出賬號(hào)上還是轉(zhuǎn)入賬號(hào)上,都顯得很別扭。
類似這樣用來形容動(dòng)作或者行為的,應(yīng)該單獨(dú)聲明為一個(gè)服務(wù)。服務(wù)用來協(xié)調(diào),服務(wù)于實(shí)體和值對象的相關(guān)功能進(jìn)行分組。
服務(wù)擔(dān)當(dāng)了一個(gè)提供操作的接口。
模型驅(qū)動(dòng)設(shè)計(jì)——服務(wù)三個(gè)特征
1、服務(wù)執(zhí)行的操作涉及一個(gè)領(lǐng)域概念,這個(gè)領(lǐng)域概念通常不屬于一個(gè)實(shí)體或者值對象;
2、被執(zhí)行的操作涉及到領(lǐng)域中的其他對象;
3、服務(wù)的操作應(yīng)該是無狀態(tài)的,僅僅表示一個(gè)動(dòng)作或行為。如轉(zhuǎn)賬、客戶注冊、短信發(fā)送等……
模型驅(qū)動(dòng)設(shè)計(jì)——模塊
當(dāng)模型越來越大,就需要模塊來組織相關(guān)概念和任務(wù),以降低復(fù)雜度。
另一個(gè)原因提升代碼質(zhì)量。當(dāng)涉及到功能性內(nèi)聚和通信性內(nèi)聚要求時(shí),使用模塊。
模塊應(yīng)該由在功能上或邏輯上屬于一體的元素構(gòu)成,同時(shí)具有定義好的接口,提供給其他模塊訪問。
提供的接口應(yīng)該是封裝完整以后,以減少對方調(diào)用接口的數(shù)量為主要目的。
模型驅(qū)動(dòng)設(shè)計(jì)——聚合、工廠和資源庫
聚合:用來定義對象所有權(quán)和邊界的領(lǐng)域模式。
工廠和資源庫:處理對象的創(chuàng)建和存儲(chǔ)問題。
模型驅(qū)動(dòng)設(shè)計(jì)——聚合
一個(gè)模型會(huì)包含眾多領(lǐng)域?qū)ο螅I(lǐng)域?qū)ο笾g會(huì)形成一對一、一對多或?qū)Χ鄬﹃P(guān)聯(lián),從而形成了一個(gè)復(fù)雜的關(guān)系網(wǎng)。
1、刪除非本質(zhì)的關(guān)聯(lián)關(guān)系:在領(lǐng)域中存在,但在模型中不必要。
2、通過增加約束的方式消減多重性。
3、將雙向關(guān)聯(lián)轉(zhuǎn)換成非雙向的關(guān)聯(lián),如汽車擁有引擎,但引擎并不需要擁有汽車。
聚合使用邊界將內(nèi)部和外部的對象劃分開,每個(gè)聚合有一個(gè)根,也就是一個(gè)實(shí)體對象,并且它是外部可以訪問的唯一對象。
外部對其他對象的引用,全部由這個(gè)跟對象來進(jìn)行訪問。(類似于企業(yè)組織架構(gòu)之間溝通的意思,總經(jīng)理只找總監(jiān),總監(jiān)再找下面的
人。)
模型驅(qū)動(dòng)設(shè)計(jì)——工廠
當(dāng)實(shí)體的屬性很多時(shí),通過構(gòu)造器來創(chuàng)建對象是非常困難的,代碼可讀性也非常差,也與領(lǐng)域本身所做的事情相沖突。在領(lǐng)域中,某些事物通常是由其他事物組合的(如汽車裝配~~)。
這里的工廠概念與設(shè)計(jì)模式的類似,可以理解成簡單工廠模式、抽象工廠模式和工廠方法模式的并稱,因需選用對應(yīng)的模式即可。
模型驅(qū)動(dòng)設(shè)計(jì)——資源庫
資源庫:封裝所有獲取對象引用所需的邏輯。簡單來說就是對獲取數(shù)據(jù)的操作進(jìn)行封裝。
重構(gòu)與一致性
持續(xù)重構(gòu)
字面含義。
凸顯關(guān)鍵概念
讓隱式的概念顯式化。
挖掘模型概念的方式有:使用領(lǐng)域文獻(xiàn);與領(lǐng)域?qū)<疑钊虢徽劦取?br>
從代碼的例子,如HashMap擴(kuò)容的約束,判斷是否需要擴(kuò)容時(shí),將判斷條件(約束)提取為單獨(dú)的boolean方法,就是一種手段。
凸現(xiàn)關(guān)鍵概念,在領(lǐng)域建模過程中和設(shè)計(jì)過程中會(huì)交互產(chǎn)生,但并不會(huì)表現(xiàn)得很明顯,很多時(shí)候是在潛移默化的過程中發(fā)生的。
保持模型一致性
接下來的章節(jié)全部是針對跨團(tuán)隊(duì)合作大型項(xiàng)目群中出現(xiàn)的問題所提供的解決思路。
多個(gè)團(tuán)隊(duì)之間需要合作,同時(shí)又是并發(fā)開發(fā)。比如項(xiàng)目組A定義了一個(gè)模型,項(xiàng)目組B在開發(fā)時(shí)覺得少了一部分東西,于是就增加或修改了部分內(nèi)容,但項(xiàng)目組B的這位開發(fā)人員沒有意識(shí)到,這個(gè)動(dòng)作實(shí)際上是對模型進(jìn)行了變更。
如此相互修改后,就會(huì)導(dǎo)致系統(tǒng)實(shí)現(xiàn)上完全背離了當(dāng)初的設(shè)計(jì),從而導(dǎo)致系統(tǒng)出現(xiàn)問題。
想想自己平時(shí)校驗(yàn)數(shù)據(jù)的方法?用isNull還是isNotNull?
保持模型一致性——界定的上下文
每一個(gè)模型定義一個(gè)上下文,一個(gè)獨(dú)立的模型,上下文是固定的。
定義不同模型間的邊界和關(guān)系,不能在不同模型間傳遞任何對象,也不能在沒有邊界的情況下激活行為。
不同模型的代碼不能合并。模型應(yīng)該足夠小,直到只有邏輯相關(guān)以及能形成自然概念的因素放在一個(gè)模型中為止,同時(shí)也要一個(gè)小團(tuán)隊(duì)能夠?qū)崿F(xiàn)(轉(zhuǎn)換為代碼)。
萬不得已不同的團(tuán)隊(duì)要在同一個(gè)模型上工作時(shí),要時(shí)刻注意不要踩到別人的腳(各司其職,不越邊界)。
保持模型一致性——持續(xù)集成
當(dāng)界定的上下文定義以后,就需要保持模型的完整性和一致性,不能再繼續(xù)分割模型。
一開始定義模型是不完美的,一般是先創(chuàng)建模型,然后反復(fù)持續(xù)完善。
使用持續(xù)集成,確保新增的部分和模型原有的部分配合得很好,在代碼中也能被正確地實(shí)現(xiàn)。
對3—7人的獨(dú)立小團(tuán)隊(duì)而言,每日合并代碼是比較推薦的做法。
合并的代碼需要自動(dòng)構(gòu)建并執(zhí)行自動(dòng)測試(單元測試、 Mock等)。
保持模型一致性——上下文映射(Context Map)
每個(gè)團(tuán)隊(duì)在自己的模型下工作,但最好能了解所有的模型。
上下文映射是指抽象出不同界定上下文和它們之間關(guān)系的文檔,可以是一個(gè)圖,也可以是其他文檔。
保持模型一致性——共享內(nèi)核
在上下文之間,共享內(nèi)核(Shared Kernel) 和客戶-供應(yīng)商(Customer-Supplier)是具有高級(jí)交互的模式。
隔離通道(Separate Way)是在我們想讓上下文高度獨(dú)立和分開運(yùn)行時(shí)要用到的模式。
還有兩個(gè)模式處理系統(tǒng)和繼承系統(tǒng)或者外部系統(tǒng)之間的交互,它們是開放主機(jī)服務(wù)(Open Host Service)和防崩潰層(Anticorruption Layer)。
共享內(nèi)核的目的是減少重復(fù),但是仍保持兩個(gè)獨(dú)立的上下文。對于共享內(nèi)核的開發(fā)需要多加小心。
如果團(tuán)隊(duì)用的是內(nèi)核代碼的副本,那么要盡可能早地融合(Merge)代碼,至少每周一次。還應(yīng)該使用測試工具,這樣每一個(gè)針對內(nèi)核的修改都能快速地被測試。
內(nèi)核的任何改變都應(yīng)該通知另一個(gè)團(tuán)隊(duì),團(tuán)隊(duì)之間密切溝通,使大家都能了解最新的功能。
共享內(nèi)核的目的是減少重復(fù),但是仍保持兩個(gè)獨(dú)立的上下文。對于共享內(nèi)核的開發(fā)需要多加小心。
如果團(tuán)隊(duì)用的是內(nèi)核代碼的副本,那么要盡可能早地融合(Merge)代碼,至少每周一次。還應(yīng)該使用測試工具,這樣每一個(gè)針對內(nèi)核的修改都能快速地被測試。
內(nèi)核的任何改變都應(yīng)該通知另一個(gè)團(tuán)隊(duì),團(tuán)隊(duì)之間密切溝通,使大家都能了解最新的功能。
保持模型一致性——客戶與供應(yīng)商
經(jīng)常會(huì)遇到兩個(gè)子系統(tǒng)之間關(guān)系特殊的時(shí)候:一個(gè)嚴(yán)重依賴另一個(gè)。
兩個(gè)子系統(tǒng)所在的上下文是不同的,而且一個(gè)系統(tǒng)的處理結(jié)果被輸入到另外一個(gè)。而且沒有共享內(nèi)核。
在兩個(gè)團(tuán)隊(duì)之間確定一個(gè)明顯的客戶/供應(yīng)商關(guān)系。在計(jì)劃場景里,讓客戶團(tuán)隊(duì)扮演和供應(yīng)商團(tuán)隊(duì)打交道的客戶角色。
為客戶需求做充分的解釋和任務(wù)規(guī)劃,讓每個(gè)人理解相關(guān)的約定和日程表。
聯(lián)合開發(fā)可以驗(yàn)證期望(Expected)接口的自動(dòng)化驗(yàn)收測試。
保持模型一致性——順從者
供應(yīng)商團(tuán)隊(duì)并不一定會(huì)做得很好。利他精神、自己的deadline等會(huì)導(dǎo)致為客戶團(tuán)隊(duì)服務(wù)的力度不夠,客戶團(tuán)隊(duì)大多數(shù)情況下比較無助。
如果客戶不得不使用供應(yīng)商團(tuán)隊(duì)的模型,而且這個(gè)模型做得很好,那么就需要順從了,而且要完全順從。
這個(gè)模式與共享內(nèi)核相似,但最大的區(qū)別是,客戶團(tuán)隊(duì)沒有任何權(quán)限修改模型。
示例:一般稱為“核心”系統(tǒng)會(huì)作為供應(yīng)商,“前置”“應(yīng)用”等為客戶。常用做法是提供一個(gè)大而全的模型,甚至還會(huì)提供一個(gè)JSON串用來防止臨時(shí)需要的發(fā)生。
保持模型一致性——防崩潰層
不同團(tuán)隊(duì)之間會(huì)有模型交互,如接口。
不能忽視和外部模型的交互,但是我們也應(yīng)該小心地將我們的模型和它隔離開來。方法就是在自己的客戶端模型和外部模型之間,建立一個(gè)防崩潰層。
實(shí)現(xiàn):一個(gè)非常好的方案是將這個(gè)層看作從客戶端模型來的一個(gè)服務(wù)。
一般來說是Facade和Adapter的組合。
保持模型一致性——獨(dú)立方法
獨(dú)立方法模式適合一個(gè)企業(yè)應(yīng)用可由幾個(gè)較小的應(yīng)用組成,而且從建模的角度來看彼此之間有很少或者沒有相同之處的情況。
創(chuàng)建獨(dú)立的界定上下文(Bounded Context),并獨(dú)立建模。
這樣做的好處是有選擇實(shí)現(xiàn)技術(shù)的自由。如有些團(tuán)隊(duì)使用Java、有些團(tuán)隊(duì)使用Node.js、還有些團(tuán)隊(duì)使用Go或者Ruby等。
然后再通過一個(gè)瘦的GUI或者類似門戶的形式,將這樣的小應(yīng)用組合起來,通過按鈕點(diǎn)擊不同系統(tǒng)結(jié)合單點(diǎn)登錄的方式來使用。
保持模型一致性——開放主機(jī)服務(wù)
集成兩個(gè)子系統(tǒng)時(shí),通常要在它們之間創(chuàng)建一個(gè)轉(zhuǎn)換層,用來扮演緩沖的角色。但如果需要集成的系統(tǒng)較多,就是一個(gè)災(zāi)難。系統(tǒng)難以維護(hù),調(diào)整極度困難。
定義一個(gè)能以服務(wù)的形式訪問子系統(tǒng)的協(xié)議。開放它,使得所有需要和你集成的人都能獲取到。然后優(yōu)化和擴(kuò)展這個(gè)協(xié)議,使其可以處理新的集成需求,但某團(tuán)隊(duì)有特殊需求時(shí)除外。
特殊的需求在協(xié)議之上再建立一個(gè)轉(zhuǎn)換層。
這種做法,叫開放主機(jī)服務(wù)。
保持模型一致性——精煉
一個(gè)大的領(lǐng)域會(huì)有一個(gè)大的模型。即使在重構(gòu)多次之后,也依然會(huì)
很大。
對于這樣的情況,就需要精煉。
思路是定義一個(gè)代表領(lǐng)域本質(zhì)的核心域(Core Domain)。精煉過程的副產(chǎn)品將是組合領(lǐng)域中其他部分的普通子域(Generic Subdomain) 。
在之前的例子中,將進(jìn)站站點(diǎn)和出站站點(diǎn)統(tǒng)一為經(jīng)緯度坐標(biāo)的過程,其實(shí)就是一次精煉的過程。
