應(yīng)用程序定義
“應(yīng)用程序”表示那些支撐核心域模型的組件,通常包括領(lǐng)域模型本身,用戶界面,內(nèi)部使用的應(yīng)用服務(wù)和基礎(chǔ)設(shè)施組件等
這是書中的定義,理解起來(lái)跟通常意義的“應(yīng)用程序”也是一樣的,就是一個(gè)可以用的軟件,就是完成一定業(yè)務(wù)功能的完整的程序,就是把領(lǐng)域模型、領(lǐng)域服務(wù)、應(yīng)用服務(wù)、資源庫(kù)、基礎(chǔ)設(shè)施加上用戶界面組合起來(lái),構(gòu)成一個(gè)整體。形式可以有很多種,可以是桌面應(yīng)用程序或web應(yīng)用程序乃至移動(dòng)端App。當(dāng)然使用bash編寫一個(gè)腳本,實(shí)現(xiàn)一定功能,其實(shí)也算是一個(gè)應(yīng)用程序,只是一般太簡(jiǎn)單了,不屬于本章討論的應(yīng)用程序范圍。
下圖來(lái)一個(gè)應(yīng)用程序的典型構(gòu)成:

這是我理解畫出的,跟書中的應(yīng)用程序的主要方面的圖并不一樣。用戶界面通過(guò)應(yīng)用服務(wù)實(shí)現(xiàn)業(yè)務(wù)功能,應(yīng)用服務(wù)把操作委托給領(lǐng)域模型(實(shí)體、值對(duì)像、聚合)、領(lǐng)域服務(wù)(可能不存在)、資源庫(kù)。領(lǐng)域模型、領(lǐng)域服務(wù)、資源庫(kù)是領(lǐng)域要素,是通用語(yǔ)言的表達(dá),是接口定義。而真正的技術(shù)實(shí)現(xiàn)都由基礎(chǔ)設(shè)施實(shí)現(xiàn),基礎(chǔ)設(shè)施屏蔽領(lǐng)域概念與技術(shù)實(shí)現(xiàn),基礎(chǔ)設(shè)施直接跟技術(shù)組件打交道,比如存儲(chǔ)的DB、消息中間件等,領(lǐng)域模型(包括領(lǐng)域服務(wù)和資源庫(kù))是不直接跟技術(shù)組件打交道的。領(lǐng)域服務(wù)和資源庫(kù),會(huì)使用和組合領(lǐng)域模型(主要是聚合),分別完成業(yè)務(wù)邏輯和聚合存取。
用戶界面
用戶界面,稱為UI,而一般為了給終端用戶帶來(lái)更好的操作體驗(yàn),會(huì)實(shí)現(xiàn)帶圖形操作接口,即GUI,可以表現(xiàn)為web應(yīng)用或桌面應(yīng)用和移動(dòng)端應(yīng)用。
用戶界面通常需要渲染多個(gè)聚合實(shí)例中的屬性,但用戶一次只會(huì)修改其中一個(gè)聚合實(shí)例。
可以通過(guò)數(shù)據(jù)傳輸對(duì)象(Data Transfer Object,DTO)或領(lǐng)域負(fù)載對(duì)象(Domain Payload Object,DPO)來(lái)組裝多個(gè)聚合實(shí)例,DTO直接拷貝屬性,類似深拷貝,DPO只拷貝聚合實(shí)體引用,所以前者適合需要序列化的場(chǎng)景(如RPC),后者適合單虛擬機(jī)應(yīng)用架構(gòu)中。
如果多聚合組裝邏輯比較復(fù)雜且成本高,可以使用CQRS架構(gòu)。
使用調(diào)停者發(fā)布聚合的內(nèi)部狀態(tài)。
可以使用數(shù)據(jù)轉(zhuǎn)換器來(lái)處理針對(duì)不同類型客戶端的輸出。
展現(xiàn)模型(Presenation Model),是區(qū)別于視圖模型的(View model),展現(xiàn)模型可以作為渲染視圖的適配器,可以跟蹤用戶的編輯,是圍繞著應(yīng)用服務(wù)的一個(gè)最小化門面。展現(xiàn)模型,其實(shí)跟MVC架構(gòu)中的C即Controller的職責(zé)很像。
應(yīng)用服務(wù)
應(yīng)用服務(wù)是領(lǐng)域模型的直接客戶。應(yīng)用服務(wù)應(yīng)該做成很薄的一層,并且只使用它們來(lái)協(xié)調(diào)對(duì)模型的任務(wù)操作。
應(yīng)用服務(wù)負(fù)責(zé)用例流的任務(wù)協(xié)調(diào),每個(gè)用例流對(duì)應(yīng)著一個(gè)應(yīng)用服務(wù)方法。應(yīng)用服務(wù)管理著事務(wù)、安全和任務(wù)委派等操作,把操作委派給領(lǐng)域模型。
應(yīng)用服務(wù)方法參數(shù)可以直接使用領(lǐng)域?qū)ο髥??建議不使用,入?yún)⑹褂妹顚?duì)象,命令對(duì)象屬性使用基本數(shù)據(jù)類型,輸出使用DTO。雖然會(huì)增加很多對(duì)象的生成和釋放消耗。
應(yīng)用服務(wù)可以使用獨(dú)立接口,也可以把接口和實(shí)現(xiàn)定義在一個(gè)類中。應(yīng)用服務(wù)一般不需要使用基礎(chǔ)設(shè)施來(lái)實(shí)現(xiàn)。
基礎(chǔ)設(shè)施
基礎(chǔ)設(shè)施就是為領(lǐng)域模型(包括領(lǐng)域服務(wù)和資源庫(kù))提供技術(shù)實(shí)現(xiàn)。如前面應(yīng)用程序定義的圖形,基礎(chǔ)設(shè)施實(shí)現(xiàn)了領(lǐng)域服務(wù)、資源庫(kù)的接口乃至用戶界面,直接于技術(shù)組件打交道。
基礎(chǔ)設(shè)施的實(shí)現(xiàn)可以依賴注入或服務(wù)工廠來(lái)完成接口實(shí)現(xiàn)的查找。
企業(yè)組件容器
我也是只使用Spring的
組合多個(gè)限界上下文
組合多個(gè)限界上下文一節(jié)的問(wèn)題是:UI需要組合多個(gè)模型,而三個(gè)模型位于三個(gè)限界上下文。如果使用一個(gè)應(yīng)用服務(wù)來(lái)組合多個(gè)模型,因?yàn)槭羌啥鄠€(gè)限界上下文的,所以這時(shí)候應(yīng)用服務(wù)其實(shí)需要內(nèi)建一個(gè)防腐層,并且“映射”出新的領(lǐng)域模型,這些新領(lǐng)域模型更多是運(yùn)載數(shù)據(jù)屬性的需要,所以容易產(chǎn)生貧血領(lǐng)域?qū)ο?。如果是?chuàng)建一些新的、清晰的限界上下文?這也增加不少?gòu)?fù)雜度,特別是會(huì)導(dǎo)致用戶接口層對(duì)多個(gè)應(yīng)用層的依賴。那到底該選擇哪種方式呢?書中也沒(méi)給出答案,只能根據(jù)自己的實(shí)際情況做決定。
而我為什么要把這一節(jié)放到最后來(lái)講,其實(shí)還想說(shuō)在微服務(wù)的思路下,服務(wù)進(jìn)程拆得很細(xì),可能一個(gè)聚合或一個(gè)資源庫(kù)實(shí)現(xiàn)就會(huì)獨(dú)立成一個(gè)服務(wù)進(jìn)程,如果這時(shí)候把一個(gè)服務(wù)進(jìn)程都當(dāng)成一個(gè)上下文,那會(huì)使得應(yīng)用程序的領(lǐng)域概念很復(fù)雜,所以下面提出一個(gè)思路,限界上下文是按領(lǐng)域來(lái)劃分按通用語(yǔ)言的表達(dá),而不是服務(wù)進(jìn)程:

左圖中的每一個(gè)非內(nèi)嵌框圖都是代表一個(gè)獨(dú)立服務(wù)進(jìn)程的微服務(wù)