- 實(shí)現(xiàn)領(lǐng)域驅(qū)動設(shè)計
- (美)沃恩·弗農(nóng)
- 2945字
- 2020-09-05 00:22:03
分層
分層架構(gòu)模式[Buschmann et al.]被認(rèn)為是所有架構(gòu)的始祖。它支持N層架構(gòu)系統(tǒng),因此被廣泛地應(yīng)用于Web、企業(yè)級應(yīng)用和桌面應(yīng)用。在這種架構(gòu)中,我們將一個應(yīng)用程序或者系統(tǒng)分為不同的層次。
在分層架構(gòu)中,我們將領(lǐng)域模型和業(yè)務(wù)邏輯分離出來,并減少對基礎(chǔ)設(shè)施、用戶界面甚至應(yīng)用層邏輯的依賴,因為它們不屬于業(yè)務(wù)邏輯。將一個復(fù)雜的系統(tǒng)分為不同的層,每層都應(yīng)該具有良好的內(nèi)聚性,并且只依賴于比其自身更低的層。[Evans,Ref,P.16]
圖4.1所示為一個典型的DDD系統(tǒng)所采用的傳統(tǒng)分層架構(gòu),其中核心域只位于架構(gòu)中的其中一層,其上為用戶界面層(User Interface)和應(yīng)用層(Application Layer),其下是基礎(chǔ)設(shè)施層(Infrastructure Layer)。

圖4.1 DDD所使用的傳統(tǒng)分層架構(gòu)
分層架構(gòu)的一個重要原則是:每層只能與位于其下方的層發(fā)生耦合。分層架構(gòu)也分為幾種:在嚴(yán)格分層架構(gòu)(Strict Layers Architecture)中,某層只能與直接位于其下方的層發(fā)生耦合;而松散分層架構(gòu)(Relaxed Layers Architecture)則允許任意上方層與任意下方層發(fā)生耦合。由于用戶界面層和應(yīng)用服務(wù)通常需要與基礎(chǔ)設(shè)施打交道,許多系統(tǒng)都是基于松散分層架構(gòu)的。
事實(shí)上,較低層也是可以和較高層發(fā)生耦合的,但這只局限于采用觀察者(Observer)模式或者調(diào)停者(Mediator)模式[Gamma et al.]的情況。較低層是絕對不能直接訪問較高層的。例如,在使用調(diào)停者模式時,較高層可能實(shí)現(xiàn)了較低層定義的接口,然后將實(shí)現(xiàn)對象作為參數(shù)傳遞到較低層。當(dāng)較低層調(diào)用該實(shí)現(xiàn)時,它并不知道實(shí)現(xiàn)出自何處。
用戶界面只用于處理用戶顯示和用戶請求,它不應(yīng)該包含領(lǐng)域或業(yè)務(wù)邏輯。有人可能會認(rèn)為,既然用戶界面需要對用戶輸入進(jìn)行驗證,那么它就應(yīng)該包含業(yè)務(wù)邏輯。事實(shí)上,用戶界面所進(jìn)行的驗證和對領(lǐng)域模型的驗證是不同的。在實(shí)體(Entites,5)中我們會講到,對于那些粗制濫造的,并且只面向領(lǐng)域模型的驗證行為,我們依然應(yīng)該予以限制。
如果用戶界面使用了領(lǐng)域模型中的對象,那么此時的領(lǐng)域?qū)ο髢H限于數(shù)據(jù)的渲染展現(xiàn)。在采用這種方式時,可以使用展現(xiàn)模型(Presentation Model,14)對用戶界面與領(lǐng)域?qū)ο筮M(jìn)行解耦。
由于用戶可能是人,也可能是其他的系統(tǒng),有時用戶界面層將采用開放主機(jī)服務(wù)(13)的方式向外提供API。
用戶界面層是應(yīng)用層的直接客戶。
應(yīng)用服務(wù)(Application Services,14)位于應(yīng)用層中。應(yīng)用服務(wù)和領(lǐng)域服務(wù)(Domain Services,7)是不同的,因此領(lǐng)域邏輯也不應(yīng)該出現(xiàn)在應(yīng)用服務(wù)中。應(yīng)用服務(wù)可以用于控制持久化事務(wù)和安全認(rèn)證,或者向其他系統(tǒng)發(fā)送基于事件的消息通知,另外還可以用于創(chuàng)建郵件以發(fā)送給用戶。應(yīng)用服務(wù)本身并不處理業(yè)務(wù)邏輯,但它卻是領(lǐng)域模型的直接客戶。應(yīng)用服務(wù)是很輕量的,它主要用于協(xié)調(diào)對領(lǐng)域?qū)ο蟮牟僮鳎热?span id="xwhfkrg" class="emphasis_bold">聚合(10)。同時,應(yīng)用服務(wù)是表達(dá)用例和用戶故事(user story)的主要手段。因此,應(yīng)用服務(wù)的通常用途是:接收來自用戶界面的輸入?yún)?shù),再通過資源庫(12)獲取到聚合實(shí)例,然后執(zhí)行相應(yīng)的命令操作,比如:

如果應(yīng)用服務(wù)比上述功能復(fù)雜許多,這通常意味著領(lǐng)域邏輯已經(jīng)滲透到應(yīng)用服務(wù)中了,此時的領(lǐng)域模型將變成貧血模型。因此,最佳實(shí)踐是將應(yīng)用層做成很薄的一層。當(dāng)需要創(chuàng)建新的聚合時,應(yīng)用服務(wù)應(yīng)該使用工廠(Factory,11)或聚合的構(gòu)造函數(shù)來實(shí)例化對象,然后采用資源庫對其進(jìn)行持久化。應(yīng)用服務(wù)還可以調(diào)用領(lǐng)域服務(wù)來完成和領(lǐng)域相關(guān)的任務(wù)操作,但此時的操作應(yīng)該是無狀態(tài)的。
當(dāng)領(lǐng)域模型用于發(fā)布領(lǐng)域事件(Domain Events,8)時,應(yīng)用層可以將訂閱方注冊到任意數(shù)量的事件上,這樣的好處是可以對事件進(jìn)行存儲和轉(zhuǎn)發(fā)。同時,領(lǐng)域模型只需要關(guān)注自己的核心邏輯;領(lǐng)域事件發(fā)布器(Domain Event Publisher,8)也可以保持輕量化,而不用依賴于消息機(jī)制的基礎(chǔ)設(shè)施。
我們將在另外的章節(jié)中講到領(lǐng)域模型對業(yè)務(wù)邏輯的處理。然而,在傳統(tǒng)的分層架構(gòu)中,卻存在著一些與領(lǐng)域相關(guān)的挑戰(zhàn)。在分層架構(gòu)中,領(lǐng)域?qū)踊蚨嗷蛏俚匦枰褂没A(chǔ)設(shè)施層。這里我并不是說核心的領(lǐng)域?qū)ο髸苯訁⑴c其中,而是說領(lǐng)域?qū)又械挠行┙涌趯?shí)現(xiàn)依賴于基礎(chǔ)設(shè)施層。
比如,資源庫接口的實(shí)現(xiàn)需要基礎(chǔ)設(shè)施層提供的持久化機(jī)制。那么,如果我們將資源庫接口直接實(shí)現(xiàn)在基礎(chǔ)設(shè)施層會怎樣呢?由于基礎(chǔ)設(shè)施層位于領(lǐng)域?qū)又拢瑥幕A(chǔ)設(shè)施層向上引用領(lǐng)域?qū)觿t違反了分層架構(gòu)的原則。遵從分層架構(gòu)原則并不意味著領(lǐng)域?qū)ο笮枰c基礎(chǔ)設(shè)施層發(fā)生直接耦合,此時我們可以采用模塊(9)的方式來隱藏技術(shù)實(shí)現(xiàn)細(xì)節(jié):
com.saasovation.agilepm.domain.model.product.impl
在模塊(9)章節(jié)中,我們會講到,MongoProductRepository將被放置在以上的包中。然而,這并不是解決問題的唯一辦法,我們還可以將資源庫的接口實(shí)現(xiàn)放在應(yīng)用層中,這樣便可以維持分層架構(gòu)的原則,如圖4.2所示。

圖4.2 對于領(lǐng)域?qū)又卸x的接口,其實(shí)現(xiàn)可以放在應(yīng)用層中。
還有更好的方法,請參考下文的“依賴倒置原則”。
在傳統(tǒng)的分層架構(gòu)中,基礎(chǔ)設(shè)施層位于底層,持久化和消息機(jī)制便位于該層中。這里的消息包含了消息中間件所發(fā)的消息、基本的電子郵件(SMTP)或者文本消息(SMS)。可以將基礎(chǔ)設(shè)施層中所有的組件和框架看作是應(yīng)用程序的低層服務(wù),較高層與該層發(fā)生耦合以重用技術(shù)上的基礎(chǔ)設(shè)施。即便如此,我們依然應(yīng)該避免核心的領(lǐng)域模型對象與基礎(chǔ)設(shè)施層發(fā)生直接耦合。
SaaSOvation的開發(fā)團(tuán)隊發(fā)現(xiàn),將基礎(chǔ)設(shè)施層放在最底層是存在缺點(diǎn)的。比如,此時領(lǐng)域?qū)又械囊恍┘夹g(shù)實(shí)現(xiàn)是令人頭疼的,因為他們違背了分層架構(gòu)的基本原則。再者,很難為這樣的實(shí)現(xiàn)編寫測試。他們應(yīng)該如何應(yīng)對呢?

如果我們調(diào)整一下分層架構(gòu)中各層的順序,結(jié)果會有所改觀嗎?
依賴倒置原則
有一種方法可以改進(jìn)分層架構(gòu)——依賴倒置原則(Dependency Inversion Principle,DIP),它通過改變不同層之間的依賴關(guān)系達(dá)到改進(jìn)目的。依賴倒置原則由Robert C. Martin提出[Martin,DIP],正式的定義為:
高層模塊不應(yīng)該依賴于低層模塊,兩者都應(yīng)該依賴于抽象。
抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
根據(jù)該定義,低層服務(wù)(比如基礎(chǔ)設(shè)施層)應(yīng)該依賴于高層組件(比如用戶界面層、應(yīng)用層和領(lǐng)域?qū)樱┧峁┑慕涌凇T诩軜?gòu)中采用依賴倒置原則有很多種表達(dá)方式,這里我們將采用圖4.3中的方式。

圖4.3 在使用依賴倒置原則時的一種分層方式。我們將基礎(chǔ)設(shè)施層放在所有層的最上方,這樣它可以實(shí)現(xiàn)所有其他層中定義的接口。
依賴倒置原則真的可以支持所有的層嗎?
有人認(rèn)為,在依賴倒置原則中只存在兩層,一層位于最上方,一層位于最下方。上方層將實(shí)現(xiàn)由下方層定義的抽象接口。按此對圖4.3進(jìn)行調(diào)整,基礎(chǔ)設(shè)施層將位于最上方,用戶界面層、應(yīng)用層和領(lǐng)域?qū)訉⒆鳛橄嗤囊粚樱⑶椅挥谙路健Υ耍憧梢员A糇约旱囊庖姟2灰獡?dān)心,我們將在六邊形[Cockburn]或端口和適配器架構(gòu)中對此做詳細(xì)講解。
對于圖4.3中的架構(gòu),我們可以在領(lǐng)域?qū)又卸x資源庫接口,然后在基礎(chǔ)設(shè)施層中實(shí)現(xiàn)該接口:


我們應(yīng)該將關(guān)注點(diǎn)放在領(lǐng)域?qū)由希捎靡蕾嚨怪迷瓌t,使領(lǐng)域?qū)雍突A(chǔ)設(shè)施層都只依賴于由領(lǐng)域模型所定義的抽象接口。由于應(yīng)用層是領(lǐng)域?qū)拥闹苯涌蛻簦鼘⒁蕾囉陬I(lǐng)域?qū)咏涌冢⑶议g接地訪問資源庫和由基礎(chǔ)設(shè)施層提供的實(shí)現(xiàn)類。應(yīng)用層可以采用不同的方式來獲取這些實(shí)現(xiàn),包括依賴注入(Dependency Injection)、服務(wù)工廠(Service Factory)和插件(Plug In)[Fowler,P of EAA]。本書的所有例子都采用Spring提供的依賴注入功能,有時也會采用DomainRegistry類所提供的服務(wù)工廠。事實(shí)上,DomainRegistry也是使用Spring來完成對bean的查找的,這些bean實(shí)現(xiàn)了由領(lǐng)域模型所定義的接口,包括資源庫和領(lǐng)域服務(wù)。
有趣的是,當(dāng)我們在分層架構(gòu)中采用依賴倒置原則時,我們可能會發(fā)現(xiàn),事實(shí)上已經(jīng)不存在分層的概念了。無論是高層還是低層,它們都只依賴于抽象,好像把整個分層架構(gòu)給推平了一樣。如果我們將分層架構(gòu)推平,再向其中加入一些對稱性會變得如何?請繼續(xù)往下讀。
- 數(shù)據(jù)庫基礎(chǔ)教程(SQL Server平臺)
- 云計算環(huán)境下的信息資源集成與服務(wù)
- 大數(shù)據(jù):規(guī)劃、實(shí)施、運(yùn)維
- Learn Unity ML-Agents:Fundamentals of Unity Machine Learning
- 數(shù)據(jù)庫原理與設(shè)計(第2版)
- Lego Mindstorms EV3 Essentials
- Oracle 12c云數(shù)據(jù)庫備份與恢復(fù)技術(shù)
- TextMate How-to
- MySQL技術(shù)內(nèi)幕:SQL編程
- 深入理解InfluxDB:時序數(shù)據(jù)庫詳解與實(shí)踐
- Filecoin原理與實(shí)現(xiàn)
- 中國云存儲發(fā)展報告
- 數(shù)據(jù)挖掘算法實(shí)踐與案例詳解
- 數(shù)據(jù)庫原理及應(yīng)用:SQL Server 2016
- 成功之路:ORACLE 11g學(xué)習(xí)筆記