有位朋友最近在為企業(yè)做領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design)內(nèi)訓(xùn)時(shí),遇到一位資深學(xué)員向他抱怨該技術(shù) “每次一聽就會(huì)埃篓,一用就不會(huì)”!回想到自己也曾在不同場合下聽到人們對領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的各種爭辯:掌握它的人覺得這沒什么復(fù)雜的根资,不過是一種很自然的設(shè)計(jì)方法選擇架专;而另外的人卻在抱怨這一技術(shù)過于晦澀、在現(xiàn)實(shí)中根本無處著手玄帕。到底是什么造就了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)在人們的心中有如此大的gap部脚?本文嘗試回答一下這個(gè)問題!
對某一事物的認(rèn)知差異裤纹,往往來自于人們所處的環(huán)境差異和經(jīng)驗(yàn)差異委刘。所以,讓我們從領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的由來說起鹰椒,從歷史中追溯人們的環(huán)境和經(jīng)驗(yàn)差異源自何處锡移。
在瀑布過程大行其道的時(shí)候,軟件設(shè)計(jì)和軟件開發(fā)是兩個(gè)獨(dú)立的階段漆际。軟件設(shè)計(jì)階段使用某種設(shè)計(jì)范式對領(lǐng)域(要解決的問題域)進(jìn)行抽象淆珊,給出設(shè)計(jì)模型。隨后的軟件開發(fā)階段則用對應(yīng)的編程語言和技術(shù)框架實(shí)現(xiàn)該設(shè)計(jì)模型奸汇。
在上述過程中施符,人們普遍認(rèn)為分析設(shè)計(jì)階段是核心钞支,分析設(shè)計(jì)得到的模型往往決定了軟件能否正確地解決領(lǐng)域問題,因此這一階段需要業(yè)務(wù)專家和資深的軟件工程師的協(xié)作操刀。分析設(shè)計(jì)階段的軟件工程師往往被委以“架構(gòu)師”之類高大上的名稱烁挟。
分析設(shè)計(jì)階段采用什么樣的軟件設(shè)計(jì)范式建模領(lǐng)域,反映了人們透過軟件看待現(xiàn)實(shí)世界的思維模式骨坑。
面向過程范式認(rèn)為一切皆過程
撼嗓,在這里現(xiàn)實(shí)世界的問題被分解為一個(gè)個(gè)的過程,最終通過串聯(lián)它們來解決問題欢唾。然而隨著計(jì)算機(jī)軟件開始大規(guī)模地解決復(fù)雜的商業(yè)問題且警,這種設(shè)計(jì)范式被證明缺乏足夠的模塊化和抽象手段。過程和被操作數(shù)據(jù)的分離導(dǎo)致軟件容易走向違背高內(nèi)聚礁遣、低耦合
的方向斑芜,帶來維護(hù)成本劇增。雖然如此祟霍,對于一些簡單的符合事務(wù)腳本模型
的程序用這種范式來描述仍是最自然的杏头,這也就是為什么即使像Ruby這種純面向?qū)ο笳Z言仍舊允許在頂層寫零散的過程式代碼。
面向?qū)ο蠓妒酵ㄟ^對象
來建模世界沸呐。對象有自己的屬性和接口醇王,很容易和現(xiàn)實(shí)世界中的事物相映射。對象通過封裝緊密依賴的屬性和行為崭添,只允許通過公開接口進(jìn)行交互的特性寓娩,為軟件設(shè)計(jì)提供了一種邏輯層面的模塊化手段。對象通過組合可以表示更復(fù)雜的概念呼渣,對象通過接口的泛化可以表示更抽象的概念棘伴。人們用面向?qū)ο蠹夹g(shù)大規(guī)模地解決復(fù)雜商業(yè)問題,積累了大量的建模經(jīng)驗(yàn)屁置,并最終發(fā)展出了“統(tǒng)一建模語言(UML)”焊夸。
由于UML誕生于瀑布開發(fā)模式仍占主流的時(shí)代,所以在標(biāo)準(zhǔn)的UML過程中缰犁,軟件設(shè)計(jì)被明確分為面向?qū)ο蠓治觯∣OA)淳地,面向?qū)ο笤O(shè)計(jì)(OOD)和面向?qū)ο缶幋a(OOP)階段。實(shí)際操作中OOD的工作往往被OOA和OOP各自承擔(dān)了一部分帅容。OOA針對要解決的問題在領(lǐng)域中尋找并抽象合適的概念颇象,定義它們之間的關(guān)系。OOP則負(fù)責(zé)在編碼階段補(bǔ)充被OOA忽略的和領(lǐng)域無關(guān)但是和軟件開發(fā)效率息息相關(guān)的因素(軟硬件平臺(tái)并徘、數(shù)據(jù)存儲(chǔ)約束遣钳、并發(fā)、編程框架...)麦乞,進(jìn)一步按照軟件工程的要求(各種設(shè)計(jì)原則和模式)重塑了OOA給出的業(yè)務(wù)模型蕴茴。
由于在這一過程中OOA和OOP是兩個(gè)分裂的階段劝评,所以必然出現(xiàn)兩個(gè)階段的相互鉗制。人們曾經(jīng)有一度追求直接根據(jù)架構(gòu)師給出的UML設(shè)計(jì)圖自動(dòng)生成代碼倦淀。最后實(shí)踐證明蒋畜,這一做法只有在安全性蓋過成本約束并且需求相對穩(wěn)定的領(lǐng)域可以工作。而在日益復(fù)雜的商業(yè)軟件開發(fā)中撞叽,成本因素更多被消耗在軟件代碼的維護(hù)和演進(jìn)上姻成,這時(shí)能夠指導(dǎo)人們實(shí)際編碼工作的是軟件設(shè)計(jì)原則(例如SOLID)和設(shè)計(jì)模式(例如GOF),這就是面向?qū)ο蟮膶W(xué)院派和工程派之爭愿棋。在工程派眼中面向?qū)ο蠓椒ɡ?code>類比對象
更重要科展,類
是代碼層面提供模塊化
以及接口抽象
的重要手段,其次才是運(yùn)行態(tài)的對象
所表述的領(lǐng)域概念糠雨。這也就是為何遵循了良好工程化原則的面向?qū)ο蟠a中容易存在為了消除重復(fù)而產(chǎn)生的大量碎片化的類才睹,以及為了代碼靈活性而創(chuàng)造的和領(lǐng)域概念相去甚遠(yuǎn)的抽象接口。
最終上述的軟件開發(fā)過程中一般同時(shí)存在兩個(gè)模型甘邀,一個(gè)是隱藏在各種設(shè)計(jì)文檔和UML圖中的面向領(lǐng)域的設(shè)計(jì)模型琅攘,另一個(gè)是隱藏于軟件源碼中的面向?qū)崿F(xiàn)的設(shè)計(jì)模型。這兩個(gè)模型的割裂極大地阻礙了軟件產(chǎn)品的交付效率鹃答。為了解決這一問題乎澄,敏捷軟件開發(fā)方法倡議從改善軟件開發(fā)端到端的溝通方式做起。所以在敏捷開發(fā)中测摔,團(tuán)隊(duì)更傾向于被定義為一個(gè)個(gè)獨(dú)立的特性團(tuán)隊(duì)。在特性團(tuán)隊(duì)內(nèi)部解恰,業(yè)務(wù)專家锋八、架構(gòu)師、開發(fā)人員和測試人員要能夠無障礙的溝通护盈,并集體為特性的端到端交付負(fù)責(zé)挟纱。
在敏捷軟件開發(fā)中,被普遍接受的一種觀點(diǎn)是“軟件源代碼是唯一真正的設(shè)計(jì)產(chǎn)物”腐宋。一些偏重技術(shù)實(shí)踐的敏捷軟件開發(fā)方法(如XP)極大的推動(dòng)了這方面的實(shí)踐:人們優(yōu)先將精力投入到代碼上紊服,持續(xù)保持代碼的清晰和靈活性,通過重構(gòu)代碼來演進(jìn)設(shè)計(jì)模型胸竞,并通過自動(dòng)化測試來確保設(shè)計(jì)演進(jìn)的正確性和安全性欺嗤。在這種開發(fā)模式下,那些能夠做到業(yè)務(wù)卫枝、設(shè)計(jì)煎饼、編碼、測試技能互相融合的技術(shù)人員會(huì)更受到青睞校赤。傳統(tǒng)的架構(gòu)師開始遭受到質(zhì)疑吆玖,很多組織要求內(nèi)部業(yè)務(wù)專家和架構(gòu)師都要具備編碼的能力筒溃。
敏捷的這一做法在互聯(lián)網(wǎng)應(yīng)用井噴的時(shí)代中取得了成功,但在一些傳統(tǒng)的領(lǐng)域知識復(fù)雜的組織內(nèi)卻一直飽受質(zhì)疑沾乘,敏捷也曾因此被訛傳是一種完全不要設(shè)計(jì)和文檔的開發(fā)方式怜奖!這種情況一直延續(xù)到了Eric Evans提出《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)-軟件核心復(fù)雜性應(yīng)對之道》(后面簡稱DDD)。
在DDD中翅阵,Eric Evans認(rèn)為軟件開發(fā)中最核心的資產(chǎn)應(yīng)該是領(lǐng)域模型
烦周,軟件開發(fā)中的所有參與者都應(yīng)該圍繞著一個(gè)統(tǒng)一一致的領(lǐng)域模型而工作。為了得到領(lǐng)域模型怎顾,DDD擁抱了敏捷軟件開發(fā)方法读慎。首先DDD要求軟件開發(fā)中的各種角色要緊密地工作在一起,無障礙地溝通槐雾。其次DDD認(rèn)為領(lǐng)域模型需要借助演進(jìn)式設(shè)計(jì)得到夭委,在這過程中需要重構(gòu)和自動(dòng)化測試等技術(shù)實(shí)踐的協(xié)助。但同時(shí)DDD也演進(jìn)了一些敏捷中的觀念募强,領(lǐng)域模型不是設(shè)計(jì)文檔株灸,但也不是代碼!如果我們承認(rèn)軟件開發(fā)的本質(zhì)是一個(gè)學(xué)習(xí)過程擎值,那么所有參與者對軟件如何解決領(lǐng)域問題在腦海中所構(gòu)建的一致畫面才是關(guān)鍵慌烧!在DDD中,這體現(xiàn)在通用語言(Ubiquitous Language)
中鸠儿,業(yè)務(wù)專家和開發(fā)人員通過領(lǐng)域模型走查每個(gè)用例的時(shí)候所采用的術(shù)語以及腦海中對應(yīng)的認(rèn)識應(yīng)該是高度一致的屹蚊。
從上可見DDD首先應(yīng)該是一種軟件開發(fā)過程,它擁抱了敏捷開發(fā)方法进每,采用演進(jìn)式設(shè)計(jì)和各種先進(jìn)的軟件技術(shù)實(shí)踐汹粤,追求一個(gè)統(tǒng)一一致的領(lǐng)域模型(而不是曾經(jīng)分裂的分析模型和實(shí)現(xiàn)模型),目標(biāo)是做到模型既設(shè)計(jì)田晚、代碼與設(shè)計(jì)保持一致嘱兼!
同時(shí),DDD發(fā)展了敏捷贤徒,它顯示地把領(lǐng)域和設(shè)計(jì)放到了軟件開發(fā)的核心芹壕,業(yè)務(wù)人員和軟件開發(fā)人員被得到同樣的重視,他們合作來構(gòu)建領(lǐng)域模型接奈。這讓敏捷開發(fā)方法真正的在領(lǐng)域知識復(fù)雜的行業(yè)內(nèi)得以有效應(yīng)用踢涌。
為了做到在代碼中凸顯領(lǐng)域模型,DDD提出了分層架構(gòu)鲫趁。首先代碼中需要把用戶界面斯嚎、調(diào)度框架、基礎(chǔ)設(shè)施等與領(lǐng)域無關(guān)的實(shí)現(xiàn)元素分離到不同的層次中去,讓領(lǐng)域?qū)又械拇a可以和領(lǐng)域模型保持高度一致堡僻!然后DDD從戰(zhàn)略和戰(zhàn)術(shù)兩個(gè)層面給出了可以得到領(lǐng)域模型的一些最佳實(shí)踐糠惫。
由于通用語言
的重要性,所以需要讓每個(gè)概念在各自上下文中是清晰無歧義的钉疫,于是DDD在戰(zhàn)略上提出了劃分Bounded Context硼讽。從實(shí)踐的角度看,Kent Beck很早在《實(shí)現(xiàn)模式》中說過如果一個(gè)類中的屬性被它不同接口訪問的內(nèi)聚度不同或者訪問頻率不同牲阁,就應(yīng)該將這些屬性和接口拆分出來形成一個(gè)新類固阁。這些新類往往和原有的類表示一個(gè)概念的不同方面,例如OrderedBook
城菊、DeliveredBook
备燃。當(dāng)如此需要依賴前綴區(qū)分的概念逐漸變多則代表著一種味道,提醒著我們需要考慮將它們拆分到不同的BC中去凌唬。最終這些概念在每個(gè)BC下的含義又變得唯一和一致并齐,也就不再需要前綴的修飾。不同BC間通過Context Mapping
集成在一起工作客税,每個(gè)BC都會(huì)有一個(gè)領(lǐng)域模型况褪。拆分BC的同時(shí)也分離了關(guān)注點(diǎn),降低了每個(gè)BC下領(lǐng)域模型的復(fù)雜度更耻。
對于如何獲得每個(gè)BC的領(lǐng)域模型测垛,DDD并沒有給出具體的方法。DDD在戰(zhàn)術(shù)層面只是對領(lǐng)域模型中應(yīng)該有的元素進(jìn)行了分類:Entity
秧均、Value Object
食侮、Aggregate
、Service
熬北、Factory
疙描、Repository
,并給出了每類元素在領(lǐng)域模型中的職責(zé)和特征讶隐。上述分類基本上是站在面向?qū)ο蠓妒降幕A(chǔ)上給出的,這些詞匯對沒有面向?qū)ο蠡A(chǔ)的人會(huì)顯得晦澀久又!
DDD雖然采用面向?qū)ο笤O(shè)計(jì)范式巫延,但并不意味所有場景下只有面向?qū)ο笞钸m合來構(gòu)建領(lǐng)域模型。由于領(lǐng)域模型是從領(lǐng)域問題出發(fā)人為構(gòu)建的一種面向領(lǐng)域的指示性語義地消,選擇某種基本設(shè)計(jì)范式只是選擇了一種構(gòu)建基礎(chǔ)而已炉峰。理論上選擇使用面向過程
、面向?qū)ο?/code> 還是
函數(shù)式
做為構(gòu)建基礎(chǔ)都是圖靈完備的脉执,但在工程上需要考量應(yīng)用哪種范式和要構(gòu)建的領(lǐng)域語義之間的gap最小疼阔、成本最低。另外現(xiàn)代編程語言基本都支持多范式編程,提供程序員在局部使用多種范式的自由婆廊。雖然如此迅细,主流的編程語言仍舊將面向?qū)ο笞鳛橹鞣妒剑@不僅是因?yàn)槊嫦驅(qū)ο蟮倪m應(yīng)場景更廣淘邻,人們在面向?qū)ο蠼I戏e累了大量的經(jīng)驗(yàn)茵典,更是因?yàn)槊嫦驅(qū)ο筇峁┝说统杀镜哪K化手段和抽象能力,可以讓程序員從一個(gè)不錯(cuò)的起點(diǎn)開始工作宾舅。
但遺憾的是DDD并沒有教人們?nèi)绾卧谀硞€(gè)具體領(lǐng)域找到合適的領(lǐng)域模型统阿。即使對熟悉面向?qū)ο蟮某绦騿T來說,要在領(lǐng)域中找出合理的領(lǐng)域模型也不是容易的筹我,這中間的gap往往需要實(shí)踐者在DDD之外去補(bǔ)齊扶平!
很明顯的是領(lǐng)域模型中的概念應(yīng)該來自領(lǐng)域,模型要盡可能反映領(lǐng)域本質(zhì)蔬蕊!從這個(gè)角度來說領(lǐng)域模型更應(yīng)該靠近分析模型结澄!所以傳統(tǒng)的領(lǐng)域分析技術(shù)(例如各種OOA技術(shù)和企業(yè)架構(gòu)模式)對領(lǐng)域建模都是有益的。區(qū)別是我們要確保這個(gè)模型是被代碼清晰表達(dá)的袁串,和業(yè)務(wù)專家及開發(fā)人員腦海中的理解是一致的概而,并且是需要被一直演進(jìn)著的!
由于領(lǐng)域模型的最終目的是解決領(lǐng)域問題囱修,所以任何脫離use case的領(lǐng)域建模都是無源之水赎瑰!傳統(tǒng)的分析技術(shù)在這方面已經(jīng)積累了很多經(jīng)驗(yàn),例如借助四色建钠屏可以讓我們針對要解決的問題識別出完備合理的概念和關(guān)系餐曼。另一方面模型還需要兼顧軟件復(fù)雜度和性能等其他制約因素,例如單純地問模型中Customer
和Order
是何種引用關(guān)系是沒有意義的鲜漩,一方面我們根據(jù)要解決的問題來決定誰引用誰(單向引用)或者雙向引用源譬,另一方面我們會(huì)根據(jù)實(shí)際的軟件復(fù)雜度或者性能來決定是引用地址還是引用ID,所以即使在相同的領(lǐng)域下解決的問題不同孕似,領(lǐng)域模型都是不同的踩娘。而業(yè)務(wù)專家和開發(fā)人員需要做的則是緊密配合,不斷從use case或者test case出發(fā)借助各種建模技術(shù)建立模型喉祭,根據(jù)各種約束來調(diào)整模型养渴,同時(shí)重構(gòu)代碼保持領(lǐng)域?qū)哟a和領(lǐng)域模型的一致贝次。
正如前面所說學(xué)習(xí)各種企業(yè)架構(gòu)模式以及分析模式是做好領(lǐng)域建模的必要條件苇经,但遺憾的是不同領(lǐng)域在這方面的學(xué)習(xí)曲線陡峭程度差異很大。我們能輕易從各種書或者教程中學(xué)習(xí)到的案例余佃,往往是研究得比較成熟的領(lǐng)域蔽氨,例如電子商務(wù)藐唠、人力資源管理等帆疟。類似的領(lǐng)域極端情況下去觀察沒有軟件之前人是怎么做的和記錄的,就能把涉及到的領(lǐng)域概念關(guān)系挖掘的差不多了宇立。由于這類系統(tǒng)中交互對象之間邊界天然且清晰踪宠,玩各種建模技術(shù)(例如Event Storming
)都會(huì)相對容易很多。而另一類系統(tǒng)泄伪,例如“電信系統(tǒng)”殴蓬、“能源系統(tǒng)”等,復(fù)雜度全在系統(tǒng)內(nèi)部蟋滴。這類系統(tǒng)大多比較龐大染厅,且有復(fù)雜的領(lǐng)域知識,涉及到復(fù)雜的事務(wù)津函、協(xié)議和算法肖粮。出于性能原因,往往分布式部署在各種差異化的軟硬件平臺(tái)上尔苦。這類系統(tǒng)中的各種設(shè)計(jì)模型和概念都是經(jīng)過多年沉淀后得到的結(jié)果涩馆,且相對封閉,沒有經(jīng)驗(yàn)繼承的分析建模很難設(shè)計(jì)得合理允坚。雖然按照DDD提倡的做法確實(shí)可以讓這件事做得更科學(xué)和高效魂那,但在本質(zhì)上并沒有讓這件事變得簡單!
自Eric Evans提出DDD之后稠项,這些年該技術(shù)又得到很多新的發(fā)展涯雅。人們用DCI(Data Context Interactive)架構(gòu)對DDD進(jìn)行補(bǔ)充,試圖解決領(lǐng)域?qū)ο笾行袨檫吔绾蛿?shù)據(jù)邊界不一致的問題(即service
帶來的貧血與充血之爭)展运。人們?yōu)镈DD補(bǔ)充了Domain Event
的建模元素活逆,發(fā)展出了CQRS架構(gòu)。人們提出了六邊形架構(gòu)更進(jìn)一步解耦了傳統(tǒng)的DDD分層架構(gòu)拗胜。Anyway蔗候,這些最終都沒抵上微服務(wù)架構(gòu)的出現(xiàn)對DDD帶來的推動(dòng)作用。微服務(wù)架構(gòu)從一出來就沒有很好的理論支撐如何合理的劃分服務(wù)邊界埂软,人們常常為服務(wù)要?jiǎng)澐侄啻蠖鵂幊巢恍菪庖!6鳧DD被發(fā)現(xiàn)恰好可以彌補(bǔ)微服務(wù)的營養(yǎng)不良:服務(wù)最大不要大過一個(gè)BC,否則服務(wù)內(nèi)會(huì)存在有歧義的領(lǐng)域概念勘畔;服務(wù)最小不要小過一個(gè)聚合迷殿,否則會(huì)引入分布式事務(wù)的復(fù)雜度;服務(wù)間最好通過Domain Event
來進(jìn)行交互咖杂,這樣可以讓服務(wù)保持松耦合。微服務(wù)和DDD的結(jié)合蚊夫,讓微服務(wù)架構(gòu)看起來似乎更加穩(wěn)健了诉字!但其實(shí)微服務(wù)需要的不只是DDD,微服務(wù)雖然讓某些事變得簡單了,但是構(gòu)建好微服務(wù)對軟件設(shè)計(jì)的優(yōu)秀技術(shù)實(shí)踐和基礎(chǔ)設(shè)施的要求都變高了壤圃。
對DDD的追溯就到這里陵霉,現(xiàn)在我們分析一下人們對DDD的認(rèn)知gap會(huì)發(fā)生在哪些方面!
如果你還工作在瀑布軟件開發(fā)模式下伍绳,很遺憾踊挠,即使你在做領(lǐng)域建模,你的團(tuán)隊(duì)也很難工作在一個(gè)統(tǒng)一一致的模型下冲杀。要記住DDD首先要求我們改變原有的軟件開發(fā)過程效床。
如果你的團(tuán)隊(duì)沒有領(lǐng)域?qū)<遥苓z憾权谁,你在挖掘領(lǐng)域本質(zhì)的過程中會(huì)走很多彎路剩檀,你需要找到領(lǐng)域?qū)<业膮f(xié)助或者把自己變成領(lǐng)域?qū)<摇?/p>
如果你不熟悉面向?qū)ο筌浖O(shè)計(jì),很遺憾旺芽,市面上大多DDD的教程與你無關(guān)沪猴,人們在面向?qū)ο蠼I戏e累的大量經(jīng)驗(yàn)也很難直接為你所用。
如果你不熟悉面向?qū)ο蠓治黾夹g(shù)采章,很遺憾运嗜,你的建模過程可能不是高效的,你需要通過學(xué)習(xí)和實(shí)踐來彌補(bǔ)這中間的能力gap悯舟。
如果你的團(tuán)隊(duì)編碼能力比較差担租,或者你的團(tuán)隊(duì)不具備重構(gòu)的能力和相應(yīng)的基礎(chǔ)設(shè)施,很遺憾图谷,你的模型很難落地翩活!DDD最重要的是要保持代碼和領(lǐng)域模型的一致,并且是同時(shí)演進(jìn)著的便贵!
如果你工作在相對封閉且有復(fù)雜領(lǐng)域知識的領(lǐng)域菠镇,那么你需要找到或者培養(yǎng)精通DDD的工程師,并愿意長期耕耘在該領(lǐng)域利耍!
如果你還沒有讀過《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》這本書,那么對不起隘梨,此文到此為止,你應(yīng)該先去好好讀一下這本書舷嗡!