小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道 - InfoQ

 黃爸爸好 2020-04-21

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

閱讀數(shù):38212020 年 4 月 16 日 09:49

過去幾年,通天塔項目(京東)一直處于快速的業(yè)務(wù)能力建設(shè)和架構(gòu)完善的階段,以應(yīng)對不斷增長的業(yè)務(wù)需求和容量、高可用等技術(shù)需求,現(xiàn)在通天塔平臺已經(jīng)能滿足集團主站的大部分活動、頻道搭建和運營能力,主流程的新需求越來越少,個性化需求和非標準化流程的數(shù)據(jù)源和服務(wù)接入的需求越來越多,有些甚至是京東零售體系外的,同時通天塔技術(shù)和產(chǎn)品也在積極主動尋求變化和創(chuàng)新,這些因素結(jié)合在一起驅(qū)動通天塔孵化出了一個以技術(shù)為導(dǎo)向的項目:通天塔積木,旨在構(gòu)建一個基于完全開放的前端 SDK 和后端數(shù)據(jù)源 & 服務(wù)、高度靈活和強大的積木畫布、能夠快速移植和部署到任何第三方 IT 環(huán)境的活動搭建解決方案,這套方案的初衷和設(shè)計理念也契合了京東國際化賦能和 PaaS 化的戰(zhàn)略。目前通天塔積木已經(jīng)取得階段性成果,已開始賦能京東國內(nèi)和國際站,但如何應(yīng)對異常復(fù)雜的積木業(yè)務(wù)邏輯和不可預(yù)知的業(yè)務(wù)變化,構(gòu)建業(yè)務(wù)和底層技術(shù)基礎(chǔ)實施的完全解耦的系統(tǒng),一直是我們面對的巨大挑戰(zhàn)。也是時候從更高視角來看清問題和源頭,思考一種能應(yīng)對和控制業(yè)務(wù)復(fù)雜度、具備強擴展性和彈性的解決方案??v觀我們的目標,DDD 這個詞不知不覺映入了我的眼簾。

2004 年,著名建模專家 Eric Evans 發(fā)表了他最具影響力的書籍《Domain-Driven Design –Tackling Complexity in the Heart of Software》(領(lǐng)域驅(qū)動設(shè)計—軟件核心復(fù)雜性應(yīng)對之道),書中反復(fù)強調(diào)領(lǐng)域通用語言 (Ubiquitous Language) 的重要性,全面闡述了 DDD 戰(zhàn)略設(shè)計到戰(zhàn)術(shù)設(shè)計的方法論和實踐。讓軟件研發(fā)所有參與者圍繞著一個統(tǒng)一和一致的領(lǐng)域模型建模和設(shè)計,分析模型和設(shè)計模型不在割裂,并引出了以領(lǐng)域為核心的分層架構(gòu),有效地分離業(yè)務(wù)和技術(shù)復(fù)雜度,使得領(lǐng)域?qū)拥拇a和領(lǐng)域模型保持高度一致。在戰(zhàn)術(shù)上提供了諸多元模式幫助構(gòu)建職責清晰、內(nèi)聚和高維護性和可擴展性的代碼。

領(lǐng)域驅(qū)動設(shè)計不是新鮮的概念,至今已有十六年時間,一直來不曾大行其道,直到 IT 行業(yè)內(nèi)掀起微服務(wù)的狂潮,技術(shù)界才重新審視和意識到領(lǐng)域驅(qū)動設(shè)計的價值。不能說微服務(wù)拯救了領(lǐng)域驅(qū)動設(shè)計,但確實是微服務(wù),讓領(lǐng)域驅(qū)動設(shè)計又重新煥發(fā)了青春。DDD 是一個非常龐大的建模和設(shè)計體系,這篇文章只在理論和概念上闡述 DDD 的價值、方法和架構(gòu),歡迎任何的問題指正和補充。

1、DDD 價值

1.1 應(yīng)對復(fù)雜業(yè)務(wù)

引起軟件系統(tǒng)復(fù)雜度的主要因素是需求,軟件系統(tǒng)需求又可以分兩個方面:業(yè)務(wù)需求和技術(shù)需求。我們分析系統(tǒng)的復(fù)雜度時就可以從業(yè)務(wù)復(fù)雜度和技術(shù)復(fù)雜度這兩個維度出發(fā)。

業(yè)務(wù)復(fù)雜度跟系統(tǒng)的業(yè)務(wù)需求規(guī)模和需求之間的關(guān)系層級有直接關(guān)系,需求的數(shù)量和關(guān)系的層級決定代碼的規(guī)模和邏輯循環(huán)或遞歸的層級,系統(tǒng)的需求數(shù)量越大,需求之間的關(guān)系越復(fù)雜,系統(tǒng)的業(yè)務(wù)復(fù)雜度就越大。John Ousterhout 的著作《A Philosophy of Software Design》從認知的負擔和開發(fā)工作量的角度來定義軟件系統(tǒng)的復(fù)雜度,并給出了一個復(fù)雜度公式:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

子模塊的復(fù)雜度(cp)乘以該模塊對應(yīng)的開發(fā)時間權(quán)重值(tp),累加后得到系統(tǒng)的整體復(fù)雜度(C)。可以看到系統(tǒng)整體的復(fù)雜度并不簡單等于所有子模塊復(fù)雜度的累加,還要考慮該模塊的開發(fā)維護所花費的時間在整體時間中的權(quán)重占比(tp),這個權(quán)重比就跟模塊劃分是否內(nèi)聚、設(shè)計是否優(yōu)雅有直接關(guān)系。

技術(shù)復(fù)雜度則來自于對軟件系統(tǒng)運行的質(zhì)量需求,包括安全、高性能、高并發(fā)、高可用和高擴展性。系統(tǒng)安全性要求對訪問進行控制,無論是加密還是認證和授權(quán),都需要為整個系統(tǒng)架構(gòu)添加額外的間接層。不僅對訪問的低延遲產(chǎn)生影響,還極大提升了系統(tǒng)代碼復(fù)雜度;為了讓后端系統(tǒng)能具備高擴展性和彈性,要求所有系統(tǒng)的設(shè)計必須是無狀態(tài)的;為了提升用戶端訪問體驗,后端需要增添離線任務(wù)對數(shù)據(jù)加工、異構(gòu)、預(yù)熱、預(yù)緩存,以實現(xiàn)用空間換時間,降低實時接口的邏輯復(fù)雜度來降低請求的延遲。然而最讓開發(fā)者更抓狂的是這些技術(shù)需求彼此又是相互影響甚至相互矛盾,在一些復(fù)雜流程并要求高響應(yīng)的業(yè)務(wù)場景,如下單、秒殺等,會將一個同步的訪問請求拆分為多級步驟的異步請求,再通過引入消息中間件對這些請求進行整合和分散處理,這種分離一方面增加了系統(tǒng)架構(gòu)的復(fù)雜性,另一方面也因為引入了更多的資源,使得系統(tǒng)的高可用面臨挑戰(zhàn),并增加了維護數(shù)據(jù)一致性的難度。而且技術(shù)復(fù)雜度與業(yè)務(wù)復(fù)雜度并非孤立,二者復(fù)雜度因子混合在一起產(chǎn)生的負作用更讓系統(tǒng)的復(fù)雜度變得不可預(yù)期,難以掌控,就好比氫氣和氯氣混合在一起遇到光亮發(fā)生爆炸一樣。

DDD 的核心思想就是要避免業(yè)務(wù)邏輯的復(fù)雜度與技術(shù)實現(xiàn)的復(fù)雜度混淆在一起,確定業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的邊界,從而隔離各自的復(fù)雜度,業(yè)務(wù)邏輯并不關(guān)心技術(shù)是如何實現(xiàn)的。無論采用何種技術(shù),只要業(yè)務(wù)需求不變,業(yè)務(wù)規(guī)則就不會變化。理想狀態(tài)下,應(yīng)該保證業(yè)務(wù)邏輯與技術(shù)實現(xiàn)是正交的。DDD 通過分層架構(gòu)與六邊形架構(gòu)確保業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的隔離。

DDD 戰(zhàn)略設(shè)計指導(dǎo)我們面對客戶的業(yè)務(wù)需求,由領(lǐng)域?qū)<遗c開發(fā)團隊展開充分的交流,經(jīng)過需求分析與知識提煉,獲得清晰的問題域,在引入限界上下文和上下文映射對問題域進行合理的分解,識別出核心領(lǐng)域與子領(lǐng)域,并確定領(lǐng)域的邊界以及它們之間的關(guān)系,從而把一個大的復(fù)雜系統(tǒng)問題拆分成多個細粒度、獨立和內(nèi)聚的業(yè)務(wù)子問題,從而很好地分解和控制業(yè)務(wù)復(fù)雜度,各個小組聚焦各自的子領(lǐng)域中。在架構(gòu)方面,通過分層架構(gòu)來隔離關(guān)注點,將領(lǐng)域?qū)崿F(xiàn)獨立出來,利于領(lǐng)域模型的單一性與穩(wěn)定性;引入六邊形架構(gòu)清晰地界定領(lǐng)域與技術(shù)基礎(chǔ)設(shè)施的邊界;CQRS 模式則分離了查詢場景和命令場景,針對不同場景選擇使用同步或異步操作,提高架構(gòu)的低延遲性與高并發(fā)能力。

1.1.1 分層架構(gòu)

“分層架構(gòu)”遵循了“關(guān)注點分離”原則,將屬于業(yè)務(wù)邏輯的關(guān)注點放到領(lǐng)域?qū)樱―omain Layer)中,而將支撐業(yè)務(wù)邏輯的技術(shù)實現(xiàn)放到基礎(chǔ)設(shè)施層(Infrastructure Layer)中。同時,領(lǐng)域驅(qū)動設(shè)計又頗具創(chuàng)見的引入了應(yīng)用層(Application Layer)。應(yīng)用層扮演了雙重角色。一方面它作為業(yè)務(wù)邏輯的外觀(Facade),暴露了能夠體現(xiàn)業(yè)務(wù)用例的應(yīng)用服務(wù)接口;另一方面它又是業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的粘合劑,實現(xiàn)二者之間的協(xié)作。圖“分層架構(gòu)“展現(xiàn)的就是一個典型的領(lǐng)域驅(qū)動設(shè)計分層架構(gòu)。藍色區(qū)域的內(nèi)容與業(yè)務(wù)邏輯有關(guān),灰色區(qū)域的內(nèi)容與技術(shù)實現(xiàn)有關(guān),二者涇渭分明,然后匯合在應(yīng)用層。應(yīng)用層確定了業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的邊界,通過直接依賴或者依賴注入(DI,Dependency Injection)的方式將二者結(jié)合起來。

1.1.2 六邊形架構(gòu)

由 Cockburn 提出的六邊形架構(gòu)則以“內(nèi)外分離”的方式,更加清晰地勾勒出業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的邊界,且將業(yè)務(wù)邏輯放在了架構(gòu)的核心位置。這種架構(gòu)模式改變了我們觀察系統(tǒng)架構(gòu)的視角。

體現(xiàn)業(yè)務(wù)邏輯的應(yīng)用層與領(lǐng)域?qū)犹幱诹呅渭軜?gòu)的內(nèi)核,并通過內(nèi)部的六邊形邊界與基礎(chǔ)設(shè)施的模塊隔離開。當我們在進行軟件開發(fā)時,只要恪守架構(gòu)上的六邊形邊界,就不會讓技術(shù)實現(xiàn)的復(fù)雜度污染到業(yè)務(wù)邏輯,保證了領(lǐng)域的整潔。邊界還隔離了變化產(chǎn)生的影響。如果我們在領(lǐng)域?qū)踊驊?yīng)用層抽象了技術(shù)實現(xiàn)的接口,再通過依賴注入將控制的方向倒轉(zhuǎn),業(yè)務(wù)內(nèi)核就會變得更加的穩(wěn)定,不會因為技術(shù)選型或其他決策的變化而導(dǎo)致領(lǐng)域代碼的修改。

1.2 快速響應(yīng)業(yè)務(wù)變化

不確定性和變化是這個時代的主旋律,業(yè)務(wù)需要快速上線,并根據(jù)用戶的反饋不停地調(diào)整和升級,有生命力的業(yè)務(wù)主動尋求變化,不變則亡是很多行業(yè)目前的共識,企業(yè)應(yīng)對變化的響應(yīng)力成了成敗的關(guān)鍵。同時一個長期困擾軟件研發(fā)的問題是,需求總是在變化,無論預(yù)先設(shè)計如何“精確”,總是發(fā)現(xiàn)下一個坑就在不遠處。相信很多技術(shù)人員都有這樣的經(jīng)歷,架構(gòu)和響應(yīng)能力越來越糟糕,也就是我們常說的架構(gòu)腐化了,最后大家不得不接受重寫。軟件架構(gòu)設(shè)計的另一個關(guān)鍵方面是讓系統(tǒng)能夠更快地響應(yīng)外界業(yè)務(wù)的變化,并且使得系統(tǒng)能夠持續(xù)演進。在遇到變化時不需要從頭開始,保證實現(xiàn)成本得到有效控制。

DDD 的核心是從業(yè)務(wù)出發(fā)、面向業(yè)務(wù)變化構(gòu)建軟件架構(gòu),實質(zhì)是保證面對業(yè)務(wù)變化時我們能夠有足夠快的響應(yīng)能力。面向業(yè)務(wù)變化而架構(gòu)就要求首先理解業(yè)務(wù)的核心問題,即有針對性地進行關(guān)注點分離來找到相對內(nèi)聚的業(yè)務(wù)活動形成子問題域。讓每個字問題的劃分盡可能靠近變化的原點,子問題域內(nèi)部是相對穩(wěn)定的,未來的變化頻率不會很高,是符合深模塊特性的,而子問題邊界是很容易變化的。DDD 最后在實現(xiàn)層面利用成熟的技術(shù)模式屏蔽掉技術(shù)細節(jié)的復(fù)雜度。

1.3 與微服務(wù)相得益彰

Martin Fowler 和 James Lewis 提出微服務(wù)時,提出了微服務(wù)的 9 大架構(gòu)特質(zhì),指導(dǎo)組織圍繞業(yè)務(wù)組建團隊,把業(yè)務(wù)拆分為一個個業(yè)務(wù)上高度內(nèi)聚、技術(shù)上松散耦合、運行在獨立進程中的小型服務(wù),微服務(wù)架構(gòu)賦予了每個服務(wù)業(yè)務(wù)上的敏捷性和技術(shù)上的自主性,因此可以針對每個服務(wù)進行獨立地迭代、更新、部署和彈性擴展,從而縮短需求交付周期并加速創(chuàng)新。

在面對復(fù)雜業(yè)務(wù)和快速變化需求時,DDD 從業(yè)務(wù)視角進行關(guān)注點分離和應(yīng)對復(fù)雜度的,讓業(yè)務(wù)具備更高的響應(yīng)力。DDD 戰(zhàn)略設(shè)計階段,引入限界上下文(Bounded Context)和上下文映射(Context Map)對問題域進行合理的分解,確定領(lǐng)域的邊界以及它們之間的關(guān)系,維持模型的完整性。限界上下文不僅限于對領(lǐng)域模型的控制,而在于分離關(guān)注點之后,使得整個上下文可以成為獨立部署的設(shè)計單元,這就是“微服務(wù)”的概念,上下文映射的諸多模式則對應(yīng)了微服務(wù)之間的協(xié)作。因此在戰(zhàn)略設(shè)計階段,微服務(wù)擴展了領(lǐng)域驅(qū)動設(shè)計的內(nèi)容,反過來領(lǐng)域驅(qū)動設(shè)計又能夠保證良好的微服務(wù)設(shè)計。邊界給了實現(xiàn)限界上下文內(nèi)部的最大自由度。這也是戰(zhàn)略設(shè)計在分治上起到的效用,我們可以在不同的限界上下文選擇不同的架構(gòu)模式和技術(shù)實現(xiàn),這也正好映照了微服務(wù)的特點:在技術(shù)架構(gòu)上,系統(tǒng)模塊之間充分解耦,可以自由地選擇合適的技術(shù)架構(gòu),去中心化地治理技術(shù)和數(shù)據(jù)。

Thought Works 公司技術(shù)專家編寫的《微服務(wù)設(shè)計》書中,專門有一章節(jié)“限界上下文”,充分說明微服務(wù)的落地需要 DDD 來輔助的,起碼在建模階段是需要借助 DDD 強大的戰(zhàn)略模式來支撐的。微服務(wù)不是簡單的指將服務(wù)盡可能的拆小,然后一個 RPC 框架搞定了,這太粗糙了,無法落地。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

1.4 輔助中臺戰(zhàn)略落地

領(lǐng)域驅(qū)動設(shè)計讓參與者基于統(tǒng)一語言溝通和協(xié)作,圍繞一個統(tǒng)一和一致的領(lǐng)域模型工作,傳統(tǒng)的分析模型和設(shè)計模型不再割裂;顯式地把業(yè)務(wù)領(lǐng)域和設(shè)計放到了軟件開發(fā)的核心,軟件人員和業(yè)務(wù)人員合作來構(gòu)建領(lǐng)域模型,使得軟件的交付質(zhì)量更高且維護成本更低;利用限界上下文來分解問題域,識別核心領(lǐng)域,有效分解和控制了業(yè)務(wù)的復(fù)雜度;利用 DDD 提倡的分層、六邊形等架構(gòu),分離了業(yè)務(wù)復(fù)雜度和技術(shù)復(fù)雜度,使得系統(tǒng)具備更強的擴展性和彈性;戰(zhàn)術(shù)層面提供了元模型(聚合,實體,值對象,服務(wù),工廠,倉儲)幫助構(gòu)建清晰、穩(wěn)定,能快速響應(yīng)變化和新需求能力的應(yīng)用;DDD 構(gòu)建的應(yīng)用能快速方便地切到微服務(wù);領(lǐng)域驅(qū)動設(shè)計給企業(yè)應(yīng)用帶來的穩(wěn)定性、靈活性、擴展性和應(yīng)對變化的響應(yīng)力對于建立靈活前臺、穩(wěn)固中臺能帶來巨大的幫助作用。

2、DDD 過程

領(lǐng)域驅(qū)動設(shè)計是一套面對復(fù)雜業(yè)務(wù)進行建模和設(shè)計的方法論和實踐,建立了以領(lǐng)域為核心驅(qū)動力的設(shè)計體系。領(lǐng)域驅(qū)動設(shè)計分為 2 個主要過程:戰(zhàn)略設(shè)計、戰(zhàn)術(shù)設(shè)計。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

在戰(zhàn)略設(shè)計階段,面對紛繁復(fù)雜的業(yè)務(wù)需求,領(lǐng)域?qū)<液脱邪l(fā)團隊進行緊密合作、充分溝通,進行事件風暴或場景驅(qū)動設(shè)計,分析需求并提煉知識,得到比較清晰的問題域,輸出由領(lǐng)域?qū)<液脱邪l(fā)團隊達成共識的統(tǒng)一語言(UL,Ubiquitous Language),基于統(tǒng)一語言對問題域進行分析和建模,識別業(yè)務(wù)邊界,確定限界上下文,根據(jù)限界上下文劃分獨立的領(lǐng)域,建立限界上下文彼此之間的關(guān)系,接著引入系統(tǒng)上下文 (System Context) 確定系統(tǒng)的邊界,并確定它的外部環(huán)境,包括與其集成的第三方系統(tǒng)與基礎(chǔ)設(shè)施。利用 DDD 分層架構(gòu)或六邊形架構(gòu)界定業(yè)務(wù)領(lǐng)域和技術(shù)實現(xiàn)的邊界,讓穩(wěn)定的核心領(lǐng)域模型處于架構(gòu)的最內(nèi)部,避免技術(shù)實現(xiàn)和架構(gòu)變動帶來的影響。

接著進入戰(zhàn)術(shù)設(shè)計階段,一個大的業(yè)務(wù)問題被分解為多個限界上下文(問題域),團隊視野和專注就可以聚焦到每一個內(nèi)聚的限界上下文,進行戰(zhàn)術(shù)設(shè)計。戰(zhàn)術(shù)設(shè)計的重點是利用領(lǐng)域驅(qū)動設(shè)計的元模型對領(lǐng)域的復(fù)雜性進行分解和建模。

領(lǐng)域驅(qū)動設(shè)計強調(diào)和突出了領(lǐng)域模型的重要性,通過整個領(lǐng)域驅(qū)動設(shè)計過程,綁定領(lǐng)域模型和技術(shù)模型,以保證領(lǐng)域模型和技術(shù)模型在貫穿整個軟件開發(fā)的生命周期中(需求分析、建模、架構(gòu)、設(shè)計、編碼、測試與持續(xù)重構(gòu))的強一致性。領(lǐng)域模型指導(dǎo)著軟件設(shè)計以及技術(shù)編碼實現(xiàn),接著通過重構(gòu)實踐來挖掘隱式概念,完善統(tǒng)一語言和模型,運用設(shè)計模式改進設(shè)計與開發(fā)質(zhì)量。以下是領(lǐng)域驅(qū)動設(shè)計的粗略過程:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.1 戰(zhàn)略設(shè)計

2.1.1 提煉問題域

回顧我們往日的分析和解決問題過程, 面對復(fù)雜問題,很多同學還沒完全理解問題的全貌就已經(jīng)在提出解決辦法,這些解決辦法只是針對問題的局部,經(jīng)典圖書《第五項修煉》把這種行為稱為“反應(yīng)式”的,碰到一個問題給出一個回應(yīng)辦法,而從這些問題整體來看這種方式會阻礙團隊找出最佳解決方案。DDD 作為一種建模和架構(gòu)方法,最大的突破是著重明確了區(qū)分了問題域和解決方案域,對業(yè)務(wù)問題的認知不是技術(shù)人員最擅長的,很多研發(fā)在碰到需求時,腦子本能就閃現(xiàn)表、類、服務(wù)、架構(gòu),把解決方案當終極問題來追求,而 DDD 要求研發(fā)進行痛苦的蛻變,在業(yè)務(wù)分析和領(lǐng)域建模階段忘記技術(shù)解決方案。同時 DDD 要求領(lǐng)域?qū)<液图夹g(shù)人員坐在一起通力合作、密切溝通來分析和建模,領(lǐng)域?qū)<覍I(yè)務(wù)有著深刻的理解,技術(shù)人員擅長技術(shù)實現(xiàn)和架構(gòu)設(shè)計,而領(lǐng)域?qū)<液图夹g(shù)人員由于工種的差異導(dǎo)致交流產(chǎn)生障礙,開發(fā)人員滿腦子是技術(shù)語言,領(lǐng)域?qū)<夷X子也都是業(yè)務(wù)概念,如果按照本能基于自己的專業(yè)背景進行溝通,效率太低了,即使有翻譯的角色也會產(chǎn)生理解偏差,DDD 的一個核心原則是所有人員包括領(lǐng)域?qū)<液图夹g(shù)的進行任何溝通都使用一種基于模型的通用語言(UL,Ubiquitous Language),在代碼中也是這樣。

DDD 幫助技術(shù)人員對需求進行本質(zhì)思考和理解,關(guān)注點不在是聚焦在功能上,而是理解需求的真正意圖和愿景,而非開發(fā)一個 feature,更深層次地理解隱含的愿景才能開發(fā)出真正地解決問題和創(chuàng)造價值的系統(tǒng)來。在提煉問題域過程中,領(lǐng)域?qū)<液图夹g(shù)專家通過充分交流,進行需求分析和知識提煉,獲得清晰的問題子域,識別出核心域、通用域、支撐域。通用域是開發(fā)該軟件系統(tǒng)根本競爭力所在,也是領(lǐng)域建模的重心,建議分配最精銳的研發(fā);通用域是指多個子域依賴的通用功能子域,比如權(quán)限、郵件、日志系統(tǒng)等;支撐域是指系統(tǒng)中非核心域和通用域的業(yè)務(wù)域。需求分析時從用例開始,列出達成業(yè)務(wù)目標需要的步驟,切忌跳轉(zhuǎn)到解決方案上,識別出用于構(gòu)建模型的知識,通過 UML 表示分析模型和業(yè)務(wù)模型,形成業(yè)務(wù)和技術(shù)人員達成共識的通用語言。

該階段領(lǐng)域?qū)<抑粚W⒂趩栴}域而不是解決方案,業(yè)務(wù)和技術(shù)人員基于 UL 溝通,并且考慮投入產(chǎn)出比,團隊只為核心業(yè)務(wù)進行領(lǐng)域驅(qū)動設(shè)計并創(chuàng)建 UL,訂單系統(tǒng)為下單模塊進行 DDD,訂單監(jiān)控模塊用普通的事務(wù)腳本方式來即可,我們通天塔的活動模板和積木業(yè)務(wù)非常復(fù)雜和核心,非常適合使用 DDD 來建模和架構(gòu)設(shè)計,而通天塔后端的 Man 系統(tǒng)是面向開發(fā)者進行后端和線上業(yè)務(wù)監(jiān)控的,進行 DDD 就是小題大做。

2.1.2 識別限界上下文(Bounded Context)

Eric Evans 說:“對一個大型系統(tǒng),領(lǐng)域模型的完全統(tǒng)一將是不可行的或者不劃算的?!薄DD 的構(gòu)建塊不能盲目地應(yīng)用在一個無限大的領(lǐng)域模型上,一個無限大的領(lǐng)域模型也無助于我們開發(fā)出優(yōu)質(zhì)的軟件,限界上下文是分解領(lǐng)域模型的關(guān)鍵。限界上下文是一種“分而治之”的思維,也是一種高層的抽象機制,讓人們對領(lǐng)域進行本質(zhì)思考,簡化問題和應(yīng)對復(fù)雜性。

限界上下文如同細胞,細胞是上下文,細胞壁是邊界,細胞內(nèi)的信息負責對代謝和遺傳進行調(diào)控,細胞壁對細胞起著支持和保護防御的作用,控制物質(zhì)進出,讓對細胞有用的物質(zhì)不能出來,有害的物質(zhì)也不能進入細胞。而領(lǐng)域驅(qū)動設(shè)計種的限界上下文保證領(lǐng)域模型的一致性和完整性,清晰邊界的控制力保證了領(lǐng)域的安全和穩(wěn)定。

2.1.2.1 如何識別限界上下文?

明確了系統(tǒng)的問題域和業(yè)務(wù)期望后,梳理出主要的業(yè)務(wù)流程,這些業(yè)務(wù)流程體現(xiàn)了各種參與者在這個過程中通過業(yè)務(wù)活動共同協(xié)作,最終完成具有業(yè)務(wù)價值的領(lǐng)域功能。業(yè)務(wù)流程結(jié)合了參與角色(Who)、業(yè)務(wù)活動(What)和業(yè)務(wù)價值(Why)。在業(yè)務(wù)流程的基礎(chǔ)上,我們就可以抽象出不同的業(yè)務(wù)場景,這些業(yè)務(wù)場景又由多個業(yè)務(wù)活動組成,可以利用領(lǐng)域場景分析方法剖析場景,以幫助我們識別業(yè)務(wù)活動,例如采用用例對場景進行分析,此時,一個業(yè)務(wù)活動實則就是一個用例。業(yè)務(wù)流程是一個由多個用戶角色參與的動態(tài)過程,而業(yè)務(wù)場景則是這些用戶角色執(zhí)行業(yè)務(wù)活動的靜態(tài)上下文。

接下來,我們利用領(lǐng)域場景分析的用例分析方法剖析這些場景。通過參與者(Actor)來驅(qū)動對用例的識別,這些參與者恰好就是參與到場景業(yè)務(wù)活動的角色。根據(jù)用例描述出來的業(yè)務(wù)活動應(yīng)該與統(tǒng)一語言一致,最好直接從統(tǒng)一語言中擷取。一旦準確地用統(tǒng)一語言描述出這些業(yè)務(wù)活動,我們就可以從語義相關(guān)性和功能相關(guān)性兩個方面識別業(yè)務(wù)邊界,進而提煉出初步的限界上下文。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

從不同角度看待限界上下文,限界上下文會呈現(xiàn)出對不同對象的控制力。

  • 領(lǐng)域邏輯層面:限界上下文確定了領(lǐng)域模型的業(yè)務(wù)邊界,維護了模型的完整性與一致性,從而降低系統(tǒng)的業(yè)務(wù)復(fù)雜度。

  • 團隊合作層面:限界上下文確定了團隊的工作邊界,建立了團隊之間的合作模式,提升了團隊間的協(xié)作效率,“康威定律”告訴我們,系統(tǒng)設(shè)計(產(chǎn)品結(jié)構(gòu))等同組織形式,每個設(shè)計系統(tǒng)的組織,其產(chǎn)生的設(shè)計等同于組織之間的溝通結(jié)構(gòu),限界上下文指導(dǎo)產(chǎn)生的團隊結(jié)構(gòu)的工作模式是最高效的。

  • 技術(shù)架構(gòu)層面:限界上下文確定了系統(tǒng)架構(gòu)的應(yīng)用邊界,保證了系統(tǒng)層和上下文領(lǐng)域?qū)痈髯缘囊恢滦裕⒘松舷挛闹g的集成方式。微服務(wù)中,限界上下文指導(dǎo)技術(shù)人員劃分微服務(wù)的邊界,通常一個限界上下文作為一個在獨立進程中運行的微服務(wù)。

DDD 驅(qū)動我們把每一個限界上下文設(shè)計成一個個“自治”的單元,自治要滿足四個特點:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

  • 最小完備是實現(xiàn)自治的基本條件,指的是自治單元履行的職責是根據(jù)業(yè)務(wù)價值的完整性和最小功能集進行設(shè)計的,這讓自治單元無需求助其他自治單元獲得信息,避免了不必要的依賴關(guān)系,同時也避免了不必要和不合適的職責添加到該自治單元上。

  • 自我履行意味著由自治單元自身決定要做什么。是否應(yīng)該履行某職責,由限界上下文擁有的信息來決定。站在自治單元的角度去思考:“如果我擁有了這些信息,我究竟應(yīng)該履行哪些職責?”這些職責屬于當前上下文的活動范圍,一旦超出,就該毫不猶豫地將不屬于該范圍的請求轉(zhuǎn)交給別的上下文。自我履行其實意味著對知識的掌握,為避免風險,你要履行的職責一定是你掌握的知識范疇之內(nèi)。

  • 穩(wěn)定空間指的是減少外界變化對限界上下文內(nèi)部的影響。穩(wěn)定空間符合開放封閉原則(OCP),即對修改是封閉的,對擴展是開放的,該原則其實體現(xiàn)了一個單元的封閉空間與開放空間。封閉空間體現(xiàn)為對細節(jié)的封裝與隱藏,開放空間體現(xiàn)為對共性特征的抽象與統(tǒng)一,二者共同確保了整個空間的穩(wěn)定。

  • 獨立進化指的是減少限界上下文的變化對外界的影響。用限界上下文的上下游關(guān)系來闡釋,則穩(wěn)定空間寓意下游限界上下文,無論上游怎么變,我自巋然不動。要做到獨立進化,就必須保證對外公開接口的穩(wěn)定性,因為這些接口被眾多消費者依賴和調(diào)用,一旦發(fā)生變更,就會牽一發(fā)而動全身。一個獨立進化的限界上下文,需要一個穩(wěn)定、設(shè)計良好的接口設(shè)計,并在版本上考慮了兼容與演化。

最小完備是基礎(chǔ),只有賦予了限界上下文足夠的信息,才能保證它的自我履行。穩(wěn)定空間與獨立進化則一個對內(nèi)一個對外,是對變化的有效應(yīng)對,而它們又是通過最小完備和自我履行來保障限界上下文受到變化的影響最小。

2.1.2.2 上下文映射

限界上下文僅是一種對領(lǐng)域問題域的靜態(tài)劃分,還缺少一個重要的關(guān)注點,即:限界上下文之間是如何協(xié)作的?當我們發(fā)現(xiàn)彼此協(xié)作存在問題時,說明限界上下文的劃分出現(xiàn)了問題,也是識別限界上下文的一種驗證方法。Eric Evans 將這種體現(xiàn)限界上下文協(xié)作方式的要素稱之為“上下文映射(Context Map)”,并給出了 9 種上下文映射關(guān)系:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

Open Host Service 相當于微服務(wù)之間的協(xié)作關(guān)系;防腐層(Anti-Corruption)是一種高度防御性的策略,結(jié)合門面(Facade)模式和適配器(Adapter)設(shè)計模式,將模型與其需要集成的其他模型隔離開來,以防止被頻繁變更或不穩(wěn)定的依賴模型污染和腐敗。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.1.3 架構(gòu)設(shè)計

“DDD 不需要特殊的架構(gòu),只要是能將技術(shù)問題與業(yè)務(wù)問題分離的架構(gòu)即可?!?– Eric Evans

傳統(tǒng)的三層架構(gòu)分而治之、降低耦合、提高復(fù)用,但存在弊端,業(yè)務(wù)邏輯在不同層泄露,導(dǎo)致替換某一層變得困難、難以對核心邏輯完整測試。領(lǐng)域驅(qū)動設(shè)計給出了 DDD 分層架構(gòu)、六邊形架構(gòu)、整潔架構(gòu)等分層架構(gòu),它們遵循“關(guān)注點分離”原則,旨在分離和隔離業(yè)務(wù)復(fù)雜度和技術(shù)復(fù)雜度,凸顯了領(lǐng)域模型,保證了領(lǐng)域模型的穩(wěn)定性和一致性。

2.1.3.1 DDD 分層架構(gòu)

DDD 分層架構(gòu)將屬于業(yè)務(wù)邏輯的關(guān)注點放到領(lǐng)域?qū)樱―omain Layer)中,將支撐業(yè)務(wù)邏輯的技術(shù)實現(xiàn)放到基礎(chǔ)設(shè)施層(Infrastructure Layer)中,DDD 創(chuàng)新性地引入了應(yīng)用層(Application Layer),應(yīng)用層扮演了兩重角色。一作為業(yè)務(wù)邏輯的門面(Facade),暴露了能夠體現(xiàn)業(yè)務(wù)用例的應(yīng)用服務(wù)接口,又是業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的粘合劑,實現(xiàn)二者之間的協(xié)作。下圖展現(xiàn)的是一個典型的領(lǐng)域驅(qū)動設(shè)計分層架構(gòu)。藍色區(qū)域和業(yè)務(wù)邏輯相關(guān),灰色區(qū)域與技術(shù)實現(xiàn)相關(guān),二者涇渭分明,然后匯合在應(yīng)用層。應(yīng)用層確定了業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的邊界,通過直接依賴或者依賴注入(DI,Dependency Injection)的方式將二者結(jié)合起來。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

分層架構(gòu)

我們詳細介紹 DDD 分層架構(gòu)中每一層的用意和設(shè)計:

  • 表現(xiàn)層(User Interface Layer):負責向用戶顯示信息和解釋用戶命令,完成前端界面邏輯

  • 應(yīng)用層(Application Layer)
    很薄的一層,負責展現(xiàn)層與領(lǐng)域?qū)又g的協(xié)調(diào),不包含任何的業(yè)務(wù)邏輯和業(yè)務(wù)規(guī)則,也不保留業(yè)務(wù)對象的狀態(tài),是對領(lǐng)域服務(wù)的編排和轉(zhuǎn)發(fā)。應(yīng)用層扮演了兩重角色。一作為業(yè)務(wù)邏輯的門面(Facade),暴露了能夠體現(xiàn)業(yè)務(wù)用例的應(yīng)用服務(wù)接口,又是業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的粘合劑,實現(xiàn)二者之間的協(xié)作。一個 Application Service 代表一個 Use Case,一個 Use Case 代表了一個完整的業(yè)務(wù)場景,對于外部的客戶來說,應(yīng)用層是與客戶協(xié)作的應(yīng)用服務(wù),接口代表是業(yè)務(wù)的含義。我們知道 DDD 分層架構(gòu)的主要目標是分離業(yè)務(wù)復(fù)雜度與技術(shù)復(fù)雜度,應(yīng)用層扮演的就是這樣的分界線。從設(shè)計模式的角度來理解,應(yīng)用層的 Application Service 是一個 Facade,對外部客戶,作為代表 Use Case 的整體應(yīng)用,對架構(gòu)內(nèi)部,它負責整合領(lǐng)域?qū)拥念I(lǐng)域邏輯與非業(yè)務(wù)相關(guān)的橫切關(guān)注點。

    應(yīng)用中,存在與具體的業(yè)務(wù)邏輯無關(guān),在整個系統(tǒng)中會被諸多服務(wù)調(diào)用的橫切關(guān)注點實現(xiàn),他們在職責上是內(nèi)聚的,散布在所有代碼層次中,包括異常處理、事務(wù)、監(jiān)控、日志、認證和授權(quán)等。所以與橫切關(guān)注點協(xié)作的服務(wù)應(yīng)被定義為應(yīng)用服務(wù)。

  • 領(lǐng)域?qū)樱―omain Layer)

    是業(yè)務(wù)軟件的核心所在,也是軟件架構(gòu)的核心,包含了業(yè)務(wù)所涉及的領(lǐng)域?qū)ο螅▽嶓w、值對象)、領(lǐng)域服務(wù),負責表達業(yè)務(wù)概念、業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則,具體表現(xiàn)形式就是領(lǐng)域模型。領(lǐng)域驅(qū)動設(shè)計提倡富領(lǐng)域模型,將業(yè)務(wù)邏輯歸屬到領(lǐng)域?qū)ο笊稀?/p>

  • 基礎(chǔ)設(shè)施層(Infrastructure Layer):基礎(chǔ)層為各層提供通用的技術(shù)能力,包括:為應(yīng)用層傳遞消息、提供 API 管理,為領(lǐng)域?qū)犹峁?shù)據(jù)庫持久化機制等。它還能通過技術(shù)框架來支持各層之間的交互。

2.1.3.2 整潔架構(gòu)(Clean Architecture)

整潔架構(gòu)中,同心圓代表應(yīng)用軟件架構(gòu)的不同部分,也是一種以領(lǐng)域模型為中心的架構(gòu),從里到外依次是 Entities、Use Cases、Interface Adapters、Frameworks and Drivers。整潔架構(gòu)明確了各層的依賴關(guān)系,越往里,依賴越低,越抽象,外圓代碼依賴只能指向內(nèi)圓,內(nèi)圓不知道外圓的任何事情。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.1.3.3 六邊形架構(gòu)(Hexagonal Architecture)

又稱為端口 - 適配器,六邊形架構(gòu)也是一種分層架構(gòu),不是從上下或左右分,而是從內(nèi)部和外部來分。六邊形架構(gòu)在領(lǐng)域驅(qū)動設(shè)計和微服務(wù)架構(gòu)設(shè)計中扮演了較重要的角色。六邊形架構(gòu)將系統(tǒng)分為內(nèi)部(內(nèi)部六邊形)和外部,內(nèi)部代表了應(yīng)用的業(yè)務(wù)邏輯,外部代表應(yīng)用的驅(qū)動邏輯、基礎(chǔ)設(shè)施(諸如 REST,SOAP,NoSQL,SQL,Message Queue 等)或其他應(yīng)用,UI 層、DB 層、和各種中間件層實際上是沒有本質(zhì)上區(qū)別的,都只是數(shù)據(jù)的輸入和輸出。內(nèi)部通過端口和外部系統(tǒng)通信,端口代表了一定協(xié)議,以 API 呈現(xiàn)。一個端口對應(yīng)多個適配器,對應(yīng)多個外部系統(tǒng),對這一類外部系統(tǒng)的歸納,不同的外部系統(tǒng)需要使用不同的適配器,適配器負責對協(xié)議進行轉(zhuǎn)換。六邊形架構(gòu)有一個明確的關(guān)注點,一開始就強調(diào)把重心放在業(yè)務(wù)邏輯上,外部的驅(qū)動邏輯或被驅(qū)動邏輯存在可變性、可替換性,依賴具體技術(shù)細節(jié)。而核心的業(yè)務(wù)領(lǐng)域相對穩(wěn)定,體現(xiàn)應(yīng)用的核心價值。六邊形的六并沒有實質(zhì)意義,只是為了留足夠的空間放置端口和適配器,一般端口數(shù)不會超過 4 個。適配器可以分為 2 類,“主”、“從”適配器,也可稱為“驅(qū)動者”和“被驅(qū)動者”。

代碼依賴只能使由外向內(nèi)。對于驅(qū)動者適配器(也稱主適配器,Driving Adapter),就是外部依賴內(nèi)部的。但是對于被驅(qū)動者適配器(也稱次適配器,Driven Adapter),實際是內(nèi)部依賴外部,這時需要使用依賴倒置,由驅(qū)動者適配器將被驅(qū)動者適配器注入到應(yīng)用內(nèi)部,這時端口的定義在應(yīng)用內(nèi)部,但是實現(xiàn)是由適配器實現(xiàn)。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.1.3.4 CQRS(命令與查詢職責分離)

CQRS 使用分離的接口將數(shù)據(jù)查詢操作 (Queries) 和數(shù)據(jù)修改操作 (Commands) 分離開來,這也意味著在查詢和更新過程中使用的數(shù)據(jù)模型也是不一樣的,這樣讀和寫邏輯就隔離開來了。使用 CQRS 分離了讀寫職責之后,可以對數(shù)據(jù)進行讀寫分離操作來改進性能,可擴展性和安全。DDD 和 CQRS 結(jié)合,可以分別對讀和寫建模:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

查詢模型是一種非規(guī)范化數(shù)據(jù)模型,不反映領(lǐng)域行為,只用于數(shù)據(jù)查詢和顯示。命令模型執(zhí)行領(lǐng)域行為,在領(lǐng)域行為執(zhí)行完成后通知查詢模型。如果查詢模型和領(lǐng)域模型共享數(shù)據(jù)源,則可以省略這一步;如果沒有共享數(shù)據(jù)源,可以借助于發(fā)布訂閱的消息模式通知到查詢模型,從而達到數(shù)據(jù)最終一致性。對于寫少讀多的共享類通用數(shù)據(jù)服務(wù)(如主數(shù)據(jù)類應(yīng)用)可以采用讀寫分離架構(gòu)模式。單數(shù)據(jù)中心寫入數(shù)據(jù),通過發(fā)布訂閱模式將數(shù)據(jù)副本分發(fā)到多數(shù)據(jù)中心。通過查詢模型微服務(wù),實現(xiàn)多數(shù)據(jù)中心數(shù)據(jù)共享和查詢。

通天塔從系統(tǒng)維度對數(shù)據(jù)庫進行了讀寫分離,通天塔的 C 端應(yīng)用和服務(wù)大部分是讀場景,CMS 是多寫應(yīng)用,所以 CMS 的寫走主庫,讀服務(wù)按照使用場景不同訪問不同的從庫,實時請求、同步數(shù)據(jù)到集市、數(shù)據(jù)中心等,這點也從數(shù)據(jù)庫基礎(chǔ)架構(gòu)上保證了通天塔系統(tǒng)的低延時和穩(wěn)定。

2.1.3.5 綜述

六邊形架構(gòu)的內(nèi)部六邊形、DDD 分層架構(gòu)的領(lǐng)域?qū)雍蛻?yīng)用層、以及整潔架構(gòu) Use Cases 和 Entities 區(qū)域?qū)崿F(xiàn)了核心業(yè)務(wù)邏輯。但是核心業(yè)務(wù)邏輯又由兩部分來完成:應(yīng)用層和領(lǐng)域?qū)舆壿?。領(lǐng)域?qū)訉崿F(xiàn)了最核心的業(yè)務(wù)領(lǐng)域部分的邏輯,對外提供領(lǐng)域模型內(nèi)細粒度的領(lǐng)域服務(wù),應(yīng)用層依賴領(lǐng)域?qū)訕I(yè)務(wù)邏輯,通過服務(wù)組合和編排通過 API 網(wǎng)關(guān)向前臺應(yīng)用提供粗粒度的服務(wù)。

業(yè)務(wù)需求變幻莫測,但我們總能在這些變化找出一些規(guī)律,用戶體驗、操作交互、以及業(yè)務(wù)流程的變化,往往只會導(dǎo)致 UI 層和流程的變化,總體來說,不管前端和外部如何變化,核心領(lǐng)域邏輯基本不會大變。把握好這個規(guī)律,我們就知道如何設(shè)計應(yīng)用層和領(lǐng)域?qū)樱绾芜M行邏輯劃界了。架構(gòu)模型正是通過分層方式來控制需求變化對系統(tǒng)的影響,確保從外向里受的影響逐步減小。面向用戶端的展現(xiàn)層可以快速響應(yīng)外部需求進行調(diào)整和發(fā)布,靈活多變;應(yīng)用層通過服務(wù)組合和編排實現(xiàn)業(yè)務(wù)流程的快速適配上線,以滿足不同的業(yè)務(wù)場景;領(lǐng)域?qū)邮墙?jīng)過抽象和提煉的業(yè)務(wù)原子單元,是非常穩(wěn)定的。這些架構(gòu)設(shè)計的好處是可以保證領(lǐng)域?qū)拥暮诵臉I(yè)務(wù)邏輯不會因為外部需求和流程的變動而調(diào)整,對于建立前臺靈活、中臺穩(wěn)固的架構(gòu)能力是很有好處的。下面是 Herberto Graca 的一張包含了六邊形、整潔、CQRS 等架構(gòu)的綜合圖,全面的說明了這些架構(gòu)的設(shè)計要點和不同的出發(fā)點。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.2 戰(zhàn)術(shù)設(shè)計

戰(zhàn)略設(shè)計為我們提供一種高層視角來審視我們的軟件系統(tǒng),而戰(zhàn)術(shù)設(shè)計則將戰(zhàn)略設(shè)計的成果具體化和細節(jié)化,它關(guān)注的是單個限界上下文內(nèi)部技術(shù)層面的實施。DDD 給我們提供了一整套技術(shù)工具集,包括實體、值對象、領(lǐng)域服務(wù)和資源庫等,如下:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.2.1 行為飽滿的領(lǐng)域?qū)ο?/h3>

讓我們先看幾個概念:

  • 失血模型:是僅包含屬性的 getter/setter 方法的數(shù)據(jù)載體,沒有行為和動作,業(yè)務(wù)邏輯由服務(wù)層完成。

  • 貧血模型:包括了屬性、getter/setter 方法,和不依賴于持久化的原子領(lǐng)域邏輯,依賴于持久層的業(yè)務(wù)邏輯將會放到服務(wù)層中。

  • 充血模型:包含了屬性、getter/setter 方法、大部分的業(yè)務(wù)邏輯,包括依賴于持久層的業(yè)務(wù)邏輯,所以使用充血模型的領(lǐng)域?qū)邮且蕾囉诔志脤?,服?wù)層是很薄的一層,僅僅封裝事務(wù)和少量邏輯。

  • 脹血模型:取消了 Service 層,脹血模型就是把和業(yè)務(wù)邏輯不相關(guān)的其他應(yīng)用邏輯(如授權(quán)、事務(wù)等)都放到領(lǐng)域模型中。

脹血模型是顯而易見不可取的,這里不做過多討論。失血模型是絕大數(shù)企業(yè)開發(fā)應(yīng)用的模式,一些火熱的 ORM 工具比如 Hibernate,Entity Framework 實際上助長了失血模型的擴散,而且傳統(tǒng)三層架構(gòu)中的服務(wù)層,承受了太多的職責,如事務(wù)管理、業(yè)務(wù)邏輯、權(quán)限檢查等,這違反了單一職責原則和關(guān)注分離原則,并且產(chǎn)生了大量的依賴和循環(huán)依賴,當業(yè)務(wù)復(fù)雜度上升時,服務(wù)層所包含的代碼將會非常龐大和復(fù)雜,直接導(dǎo)致了維護成本和測試成本的上升。同時也會導(dǎo)致業(yè)務(wù)邏輯、狀態(tài)會散落到在大量方法中,原本的代碼意圖會漸漸不明確,我們將這種情況稱為由失血癥引起的失憶癥,它會導(dǎo)致系統(tǒng)變得愈發(fā)復(fù)雜和難以維護。

采用領(lǐng)域模型的開發(fā)方式,將數(shù)據(jù)和業(yè)務(wù)邏輯封裝在一起,從服務(wù)層移動到領(lǐng)域?qū)I(yè)務(wù)邏輯模型中,這樣服務(wù)層可以只負責應(yīng)用邏輯(事務(wù)、日志、認證、監(jiān)控、編排等),領(lǐng)域模型可以專門負責其相關(guān)的業(yè)務(wù)邏輯,相關(guān)的業(yè)務(wù)分別內(nèi)聚到不同的領(lǐng)域模型中,與現(xiàn)實領(lǐng)域的業(yè)務(wù)對象映射,一些很有可能重復(fù)的業(yè)務(wù)代碼都會被集中到一處,降低重復(fù)代碼,提升業(yè)務(wù)邏輯的復(fù)用、可測試性和維護性。貧血模型和充血模型都是滿足數(shù)據(jù) + 行為的,應(yīng)該采用哪種模式,大家這是一個爭論了曠日持久的問題,關(guān)注點還是在于領(lǐng)域模型是否要依賴持久層,我個人還是偏重于貧血模式,依賴持久層就意味著單元測試的展開要更加困難,而且領(lǐng)域?qū)ο蟮纳芷趹?yīng)該交給外部模型才更合理。

2.2.2 領(lǐng)域驅(qū)動設(shè)計元模型

2.2.2.1 實體(Entity)

實體是一種具有唯一身份標識的對象,具有持續(xù)的生命周期,除唯一標識其他屬性是可變的。實體通過它的唯一標識被區(qū)分。例如實體訂單 Order,標識為 oderId,通天塔的活動實體 Activity,標識為 activityId。

2.2.2.2 值對象(Value Object)

當我們只關(guān)心一個模型元素的屬性時,應(yīng)把它歸類為值對象。應(yīng)該使這個模型元素能夠表示出其屬性的意義,并為它提供相關(guān)功能。建議將值對象設(shè)計成一個不變(Immutable)對象,這樣就不需要擔心并發(fā)帶來的諸如同步、沖突等問題了,這既降低了編程的難度,又可以無需引入額外的同步鎖影響程序的性能。也不要為它分配任何標識,這樣應(yīng)用也無需去管理值對象的生命周期。值對象通過比較其屬性(equals)區(qū)分是否是相同值對象。應(yīng)該盡量使用值對象來建模而不是實體對象。
在領(lǐng)域驅(qū)動設(shè)計中,提倡盡量定義值對象來替代基本類型,因為基本類型無法體現(xiàn)統(tǒng)一語言中的領(lǐng)域概念。假設(shè)一個實體定義了許多屬性,這些屬性都是基本類型,就會導(dǎo)致與這些屬性相關(guān)的領(lǐng)域行為都要放到實體中,導(dǎo)致實體的職責變得不夠單一。引入值對象后情況就不同了,我們可以利用合理的職責分配,將這些職責(領(lǐng)域行為)按照內(nèi)聚性分配到各個值對象中,這個領(lǐng)域模型就能變得協(xié)作良好。

值對象可以與其所在的實體對象保存在同一張表中,值對象的每一個屬性保存為一列;值對象也可以獨立于其所在的實體對象保存在另一張表中,值對象獲得委派主鍵,該主鍵對客戶端是不可見的。

2.2.2.3 聚合(Aggregate)

聚合中所包含的對象之間具有密不可分的聯(lián)系,一個聚合中可以包含多個實體和值對象,因此聚合也被稱為根實體。聚合是持久化的基本單位,它和資源庫具有一一對應(yīng)的關(guān)系。在聚合中,根是唯一允許外部對象保持對它的引用的元素,而邊界內(nèi)部的對象之間則可以互相引用。除根以外的其他 Entity 都有本地表示,但這些標識只有在聚合內(nèi)部才需要加以區(qū)別,因為外部對象除了根 Entity 之外看不到其他對象。在一個聚合中直接引用另外一個聚合并不是 DDD 所鼓勵的,但是我們可以通過 ID 的方式引用另外的聚合。

聚合是一個事務(wù)的邊界。如果一次業(yè)務(wù)操作涉及到了對多個聚合狀態(tài)的更改,那么應(yīng)該采用發(fā)布領(lǐng)域事件(參考下文)的方式通知相應(yīng)的聚合。此時的數(shù)據(jù)一致性便從事務(wù)一致性變成了最終一致性(Eventual Consistency)。

2.2.2.4 領(lǐng)域服務(wù)(Domain Service)

建模一個領(lǐng)域概念,把它放在實體上不合適,它放在值對象上也不合適,或者碰到跨聚合實例業(yè)務(wù)邏輯,沒辦法合理放到某個實體中的業(yè)務(wù)邏輯,領(lǐng)域服務(wù)就是應(yīng)對這些情況的服務(wù)。如果勉強地把這些重要的領(lǐng)域功能歸為 Entity 或 Value Object 的職責,那么不是歪曲了基于模型的對象的定義,就是人為地增加了一些無意義的對象;領(lǐng)域服務(wù)和上文中提到的應(yīng)用服務(wù)是不同的,領(lǐng)域服務(wù)是領(lǐng)域模型的一部分,而應(yīng)用服務(wù)不是。應(yīng)用服務(wù)是領(lǐng)域服務(wù)的客戶,它將領(lǐng)域模型變成對外界可用的軟件系統(tǒng)。如果將太多的領(lǐng)域邏輯放在領(lǐng)域服務(wù)上,實體和值對象上的業(yè)務(wù)邏輯會越來越弱,將變成貧血對象。

在分層架構(gòu)中藥區(qū)分什么時候應(yīng)該定義領(lǐng)域服務(wù),什么時候應(yīng)該定義應(yīng)用服務(wù),一個根本的判斷依據(jù)是看需要封裝的職責是否與領(lǐng)域相關(guān)。

2.2.2.5 資源庫(Repository)

資源庫用于保存和獲取聚合對象,將實際的存儲和查詢技術(shù)封裝起來,對外隱藏封裝了數(shù)據(jù)訪問機制。只為那些確實需要直接訪問的聚合提供 Repository。讓客戶始終聚焦于模型,而將所有對象的存儲和訪問操作交給 Repository 來完成。資源庫與 DAO 有些相似,但也存在顯著區(qū)別,DAO 是比 Repository 更低的一層,同時 DAO 只是對數(shù)據(jù)庫的一層很薄的封裝,而資源庫則更加具有領(lǐng)域特征,以“領(lǐng)域”為中心,所描述的是“領(lǐng)域語言”。另外,所有的實體都可以有相應(yīng)的 DAO,但并不是所有的實體都有資源庫,只有聚合才有相應(yīng)的資源庫。

2.2.2.6 領(lǐng)域事件(Repository)

在 Eric 的《領(lǐng)域驅(qū)動設(shè)計》中并沒有提到領(lǐng)域事件,領(lǐng)域事件是最近幾年才加入 DDD 生態(tài)系統(tǒng)的。在傳統(tǒng)的軟件系統(tǒng)中,對數(shù)據(jù)一致性的處理都是通過事務(wù)完成的,其中包括本地事務(wù)和全局事務(wù)。DDD 的一個重要原則便是一次事務(wù)只能更新一個聚合實例,但存在一個業(yè)務(wù)流程涉及修改多個聚合的事務(wù),怎么實現(xiàn)整個業(yè)務(wù)流程的數(shù)據(jù)一致性呢?在 DDD 中,領(lǐng)域事件便可以用于處理上述問題,此時最終一致性取代了事務(wù)一致性,通過領(lǐng)域事件的方式達到各個組件之間的數(shù)據(jù)一致性。既然是領(lǐng)域事件,他們便應(yīng)該從領(lǐng)域模型中發(fā)布,一個領(lǐng)域事件是指一個在領(lǐng)域中“有意義”的事件。領(lǐng)域事件的最終接收者可以是本限界上下文中的組件,也可以是另一個限界上下文。再進一步發(fā)展,事件驅(qū)動架構(gòu)可以演變成事件源(Event Sourcing),即對聚合的獲取并不是通過加載數(shù)據(jù)庫中的瞬時狀態(tài),而是通過重放發(fā)生在聚合生命周期中的所有領(lǐng)域事件完成。

2.2.2.7 工廠(Factories)

當創(chuàng)建一個對象或創(chuàng)建整個聚合時,如果創(chuàng)建工作很復(fù)雜,或者暴露了過多的內(nèi)部結(jié)構(gòu),則可以使用 Factory 進行封裝,應(yīng)該將創(chuàng)建復(fù)雜對象的實例和聚合的職責轉(zhuǎn)移到一個單獨的對象,這個對象本身在領(lǐng)域模型中可能沒有職責,但它仍是領(lǐng)域設(shè)計的一部分。

2.2.2.8 模塊(Modules)

可以從兩種維度來觀察模型,一是可以在 Module 中查看細節(jié),而不會被整個模型淹沒;二是觀察 Module 之間的關(guān)系,而不考慮其內(nèi)部細節(jié)。模塊之間應(yīng)該是低耦合的,而在模塊內(nèi)部則是高內(nèi)聚的。模塊并不僅僅是代碼的劃分,而且也是概念的劃分。找到一種低耦合的概念組織方式,從而可以相互獨立地理解和分析這些概念。對模型進行精化,直到可以根據(jù)高層領(lǐng)域概念對模型進行劃分,同時相應(yīng)的代碼也不會產(chǎn)生耦合。

2.2.2.9 模型關(guān)系

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.2.3 對象概念

  • VO(View Object):視圖對象,用于展示層,它的作用是把某個指定頁面(或組件)的所有數(shù)據(jù)封裝起來。

  • DTO(Data Transfer Object)

    數(shù)據(jù)傳輸對象,分布式應(yīng)用提供粗粒度的數(shù)據(jù)實體,也是一種數(shù)據(jù)傳輸協(xié)議,以減少分布式調(diào)用的次數(shù),從而提高分布式調(diào)用的性能和降低網(wǎng)絡(luò)負載,這里泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對象。RPC 對外暴露的服務(wù)涉及對象 API 就是 DTO,如 JSF(京東 RPC 框架)、Dubbo。

    對比 VO:絕大多數(shù)應(yīng)用場景下,VO 與 DTO 的屬性值基本一致,但對于設(shè)計層面來說,概念上還是存在區(qū)別,DTO 代表服務(wù)層需要接收的數(shù)據(jù)和返回的數(shù)據(jù),而 VO 代表展示層需要顯示的數(shù)據(jù)。

  • DO(Domain Object):領(lǐng)域?qū)ο螅褪菑默F(xiàn)實世界中抽象出來的有形或無形的業(yè)務(wù)實體。DO 不是簡單的 POJO,它具有領(lǐng)域業(yè)務(wù)邏輯。

  • PO(Persistent Object):持久化對象。

    對比 DO:DO 和 PO 在絕大部分情況下是一一對應(yīng)的,但也存在區(qū)別,例如 DO 在某些場景下不需要進行顯式的持久化,只駐留在靜態(tài)內(nèi)存。同樣 PO 也可以沒有對應(yīng)的 DO,比如一對多表關(guān)系在領(lǐng)域模型層面不需要單獨的領(lǐng)域?qū)ο蟆?br>下面是這些對象在系統(tǒng)架構(gòu)中的分布:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

2.2.4 Domain Primitive

Domain Primitive 是一個在特定領(lǐng)域里,擁有精準定義的、可自我驗證的、擁有豐富行為和業(yè)務(wù)邏輯的 Value Object,DP 使用業(yè)務(wù)域中的原生語言,可以是業(yè)務(wù)域的最小組成部分、也可以構(gòu)建復(fù)雜組合。Domain Primitive 是 Value Object 的進階版,在原始 VO 的基礎(chǔ)上要求每個 DP 擁有概念的整體,而不僅僅是值對象。在 VO 的 Immutable 基礎(chǔ)上增加了 Validity 和行為。

在項目中,散落在各個服務(wù)或工具類里面的代碼,都可以抽出來放在 DP 里,成為 DP 自己的行為或?qū)傩?。原則是:所有抽離出來的方法要做到無狀態(tài),比如原來是 static 的方法。如果原來的方法有狀態(tài)變更,需要將改變狀態(tài)的部分和不改狀態(tài)的部分分離,然后將無狀態(tài)的部分融入 DP。因為 DP 也是一種 Object Value,本身不能帶狀態(tài),所以一切需要改變狀態(tài)的代碼都不屬于 DP 的范疇。Domain Primitive 涉及三種手段:

  • 讓隱性的概念顯性化(Make Implicit Concepts Explicit)

通天塔活動類型就是一個簡單的 int 類型,屬于隱式概念,但活動類型包含了很多相關(guān)業(yè)務(wù)邏輯,比如類型名稱,不同類型活動具有獨特的 Icon,判斷活動類型是否是判斷等,我們把活動類型顯性化,定義為一個 Value Object。

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

  • 讓隱性的上下文顯性化(Make Implicit Context Explicit)

當要實現(xiàn)一個功能或進行邏輯判斷依賴多個概念時,可以把這些概念封裝到一個獨立地完整概念,也是一種 Object Value:

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

  • 封裝多對象行為(Encapsulate Multi-Object Behavior)

常見推薦使用 Domain Primitive 的場景有:

  • 有格式要求的 String:比如 Name,PhoneNumber,OrderNumber,ZipCode,Address 等。

  • 限制的 Integer:比如 OrderId(>0),Percentage(0-100%),Quantity(>=0)等。

  • 可枚舉的 int:比如 Status(一般不用 Enum 因為反序列化問題)。

  • Double 或 BigDecimal:一般用到的 Double 或 BigDecimal 都是有業(yè)務(wù)含義的,比如 Temperature、Money、Amount、ExchangeRate、Rating 等。

  • 復(fù)雜的數(shù)據(jù)結(jié)構(gòu):比如 Map<String, List> 等,盡量能把 Map 的所有操作包裝掉,僅暴露必要行為,如通天塔的活動 Map 類。

接口變得清晰可讀,校驗邏輯內(nèi)聚,在接口邊界外完成,無膠水代碼,業(yè)務(wù)邏輯清晰可讀,代碼變得更容易測試,也更安全。

2.3 通天塔 DDD 架構(gòu)設(shè)計

領(lǐng)域驅(qū)動設(shè)計之理論篇:應(yīng)對復(fù)雜業(yè)務(wù)和提升系統(tǒng)彈性之道

最后

DDD 不是一套框架,而是一種面向復(fù)雜問題的建模方法論和實踐,所以在代碼層面缺乏了足夠的約束,導(dǎo)致 DDD 在實際應(yīng)用中上手門檻很高,甚至可以說絕大部分人都對 DDD 的理解有所偏差。而且 DDD 諸多實踐在真正踐行時面臨很多挑戰(zhàn),首先是領(lǐng)域?qū)<液图夹g(shù)人員在建模過程中要摒棄自己固有的專業(yè)背景和思維定式,專注于問題域,基于統(tǒng)一語言緊密溝通和協(xié)作,具有深度業(yè)務(wù)領(lǐng)域理解和洞察的領(lǐng)域?qū)<液鸵粋€精通領(lǐng)域建模和架構(gòu)設(shè)計的技術(shù)團隊一樣少見,都必須經(jīng)過長時間學習和實踐的,其次技術(shù)人員必須轉(zhuǎn)變思維和架構(gòu)習慣,軟件系統(tǒng)最終交付的是業(yè)務(wù)價值,不是功能和技術(shù)方案,一切要以問題和業(yè)務(wù)為核心去建模和架構(gòu)。通天塔后端團隊在高并發(fā)和高性能應(yīng)用構(gòu)建方面有著非常豐富的經(jīng)驗,但在 DDD 實踐和享受到它的巨大價值層面我們還是剛起步,千里之行始于足下,我們正在邁出堅實一步,后續(xù)我們也會出 DDD 通天塔實踐篇,講述我們的經(jīng)驗和心得。

參考鏈接:

圖書《領(lǐng)域驅(qū)動設(shè)計》: https://book.douban.com/subject/5344973/ 
當中臺遇上 DDD: https://www./article/7QgXyp4Jh3-5Pk6LydWw 
張逸系列文章: http://

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多