DDD 戰(zhàn)術(shù)設(shè)計開源實踐

DDD

至少30年以前,一些軟件設(shè)計人員就已經(jīng)意識到領(lǐng)域建模和設(shè)計的重要性,DDD這一名詞,由埃里克·埃文斯(Eric Evans)在2003發(fā)表的《領(lǐng)域驅(qū)動設(shè)計》一書提出。這本書理論性極強,奠定了領(lǐng)域驅(qū)動設(shè)計這一綜合性軟件設(shè)計理論的基礎(chǔ)。書籍本身也成為DDD的“圣經(jīng)”。截止作者出搞該書發(fā)表已有20年。
筆者08年進入互聯(lián)網(wǎng)行業(yè),13年第一次聽說DDD,到最終落地2020年經(jīng)歷了至少8年的時間。
回顧過往的經(jīng)歷也不難發(fā)現(xiàn),在互聯(lián)網(wǎng)發(fā)展的早期,業(yè)務(wù)相對簡單,在“小步快跑,迭代試錯”的大環(huán)境下,DDD 反倒是一種“古老而緩慢”的思想。
在國內(nèi)互聯(lián)網(wǎng)高速發(fā)展的近十年來,深刻體會到C端消費互聯(lián)網(wǎng)的高速發(fā)展推動著技術(shù)的成熟,同時對傳統(tǒng)產(chǎn)業(yè)不斷深入重構(gòu),隨之帶來的業(yè)務(wù)的日益復(fù)雜。DDD的概念逐慚回到大家的視野。
DDD 不是新的技術(shù),反而是古老的,DDD其實是讓我們更準確的使用面向?qū)ο?/code>。反觀我們走過來的這十幾年,其實是在用面向?qū)ο蟮恼Z言,在寫面向過程的代碼。

復(fù)雜性的挑戰(zhàn)

Eric Evans)在2003發(fā)表的《領(lǐng)域驅(qū)動設(shè)計》一書副標題是“軟件核心復(fù)雜性應(yīng)對之道”
在軟件落地過程有很多因素會使軟件開發(fā)復(fù)雜化,但最根本的原因是問題領(lǐng)域本身的錯綜復(fù)雜。但我們無法回避這種復(fù)雜,只能控制這種復(fù)雜。

很多因素可能導致項目偏離軌道,如官僚主義、目標不清、資源不足等。但真正決定軟件復(fù)雜性的是設(shè)計方法。很多應(yīng)用的復(fù)雜性并不在技術(shù)上,而是來自領(lǐng)域本身,用戶的活動或業(yè)務(wù)。當這種領(lǐng)域復(fù)雜性在設(shè)計中沒有得到解決時,基礎(chǔ)技術(shù)的構(gòu)思再好也無濟于事。成功的設(shè)計必須系統(tǒng)地考慮這個核心方面。

所以綜上:DDD并不是銀彈,使用DDD的前提是我們的業(yè)務(wù)足夠復(fù)雜,脫離業(yè)務(wù)談DDD,都是不講武德的!

設(shè)計過程與開發(fā)過程

敏捷是一種項目管理和軟件開發(fā)的迭代方法,幫助團隊更快、更好地向客戶提供價值。
敏捷,關(guān)注流程和文化,DDD關(guān)注建模設(shè)計方法;敏捷,重人員輕文檔,DDD重視統(tǒng)一語言的共識
DDD是一種設(shè)計過程,一般和極限編程結(jié)合落地。

面向“敏捷開發(fā)過程”這一新的體系。我們假定項目遵循兩個開發(fā)實踐

  1. 迭代開發(fā)
  2. 開發(fā)人員必須與領(lǐng)域?qū)<揖哂忻芮械年P(guān)系。

極限編程[一種敏捷開發(fā)過程]承認設(shè)計決策的重要性,但強烈反對預(yù)先設(shè)計。相反,它將相當大的精力投入到促進溝通和提高快速變更能力的工作中。具有這種反應(yīng)能力之后,開發(fā)人員就可以在項目的任何階段只利用“最簡單而管用的方案”,然后不斷進行重構(gòu),一步一步做出小的設(shè)計改進,最終得到滿足客戶真正需求的設(shè)計。

敏捷宣言

image

敏捷軟件開發(fā)宣言

http://www.extremeprogramming.cn/content/xp/test-first.html

image
image

https://www.inflectra.com/Ideas/Topic/User-Stories.aspx

image

運用領(lǐng)域模型

領(lǐng)域

模型被用來描繪人們所關(guān)注的現(xiàn)實或想法的某個方面。模型是一種簡化。它是對現(xiàn)實的解釋抽象
它與解決問題密切相關(guān)的方面的抽象相關(guān),而忽略無關(guān)的細節(jié)。

每個軟件程序是為了執(zhí)行用戶的某項活動,或是滿足用戶的某種需求。這些用戶應(yīng)用軟件的問題區(qū)域就是軟件的領(lǐng)域。一些領(lǐng)域涉及物質(zhì)世界,有些領(lǐng)域則是無形的。

為了創(chuàng)建真正能為用戶活動所用的軟件,開發(fā)團隊必須運用一整套與這些活動有關(guān)的知識體系。所需的知識的廣度可能令人望而生畏,龐大而復(fù)雜的信息也可能超乎想象。模型正是解決此類信息超載問題的工具。模型這種知識形式對知識進行了選擇性的簡化和有意的結(jié)構(gòu)化。適當?shù)哪P涂梢允谷死斫庑畔⒌囊饬x,并專注于問題。

模型在領(lǐng)域設(shè)計中的作用

在DDD 中三個基本的用戶決定了模型的選擇:

  • 模型的設(shè)計的核心互相影響。模型與實現(xiàn)之間的緊密聯(lián)系才使模型變得有用:
    • 確保我們在模型中所進行的分析能夠轉(zhuǎn)化為代碼。
    • 在后期維護期間可以基于對模型的理解來解釋代碼。
  • 模型是團隊所有成員使用的通用語言中樞(共識的結(jié)果),技術(shù)團隊與領(lǐng)域?qū)<覠o翻譯溝通。
  • 模型是濃縮的知識
    • 來自軟件早期的經(jīng)驗可以作為反饋應(yīng)用到建模過程中(迭代)

軟件的核心

軟件的核心是其為用戶解決領(lǐng)域相關(guān)問題的能力。所有其他特性,不管有多么重要,都要服務(wù)于這個基本目的。(大部分開發(fā)人員只專注于技術(shù)本身不關(guān)心業(yè)務(wù)知識,只見樹木,不見森林)

有效建模的要素

  1. 模型和實現(xiàn)的綁定
  2. 建立了一種模型的語言
  3. 開發(fā)一個蘊含豐富知識的模型
  4. 提供模型

在模型日益完善的過程中,重要的概念不斷被添加到模型中,但同樣重要的是,不再使用的或不重要的概念則從模型中被移除。當一個不需要的概念與一個需要的概念有關(guān)聯(lián)時,則把重要的概念提取到一個新模型中,

  1. 頭腦風暴和實驗
    https://miro.com/app/board/uXjVOrN6CTc=/?share_link_id=959814092816
    事件風暴圖例

知識消化

知識消息不是一項孤立的活動 ,它一般是在開發(fā)人員的領(lǐng)導下,由開發(fā)人員與領(lǐng)域?qū)<医M成的團隊來完成的。他們共同收集信息,并通過消化而將知識組織為有用的形式。信息的原始資源來自于:

  1. 領(lǐng)域?qū)<翌^腦里的知識
  2. 現(xiàn)有系統(tǒng)的用戶
  3. 技術(shù)團隊以前在相關(guān)遺留系統(tǒng)或同領(lǐng)域的其他項目中積累的經(jīng)驗。

信息的形式也多種多樣

傳統(tǒng)的瀑布流方式因為沒有反饋,所以項目經(jīng)驗失敗。知識只是朝一個方向流量,而且不會累積。

模型在不斷改時的同時,也成為組織項目信息流(統(tǒng)一能用語言)的工具。模型聚集于需求分析。 它與編程和設(shè)計緊密交互。它通過良性循環(huán)加深團隊成員對領(lǐng)域的理解,使他們更透徹地理解模型,并對其進一步精化。模型永遠都不不會是完美的,因為它是一個不斷演化完善的過程。模型對理解領(lǐng)域必須是切實可用的。它們必須非常精確,以便使應(yīng)用程序易于實現(xiàn)和理解。

SDLC (System Development Life Cycle)系統(tǒng)開發(fā)生命周期

image
image

Waterffull vs agile

領(lǐng)域模型并不是按照“選建模,后實現(xiàn)”這個次序來工作的。象很多人一樣,我也反對“先設(shè)計、再構(gòu)建”這種固化的思維模式。Eric的經(jīng)驗告訴我們,真正強大的領(lǐng)域模型是隨著時間演進的,即使是最有經(jīng)驗的建模人員也往往發(fā)現(xiàn)他們是在系統(tǒng)初始版本完成之后才了最好的想法。

Martin fowler

image

http://www.agilenutshell.com/agile_vs_waterfall

DDD與架構(gòu)

架構(gòu)這個詞源于英文里的“Architecture“,源頭是土木工程里的“建筑”和“結(jié)構(gòu)”,而架構(gòu)里的”架“同時又包含了”架子“(scaffolding)的含義,意指能快速搭建起來的固定結(jié)構(gòu)。而今天的應(yīng)用架構(gòu),意指軟件系統(tǒng)中固定不變的代碼結(jié)構(gòu)、設(shè)計模式、規(guī)范組件間的通信方式。在應(yīng)用開發(fā)中架構(gòu)之所以是最重要的第一步,因為一個好的架構(gòu)能讓系統(tǒng)安全、穩(wěn)定、快速迭代。在一個團隊內(nèi)通過規(guī)定一個固定的架構(gòu)設(shè)計,可以讓團隊內(nèi)能力參差不齊的同學們都能有一個統(tǒng)一的開發(fā)規(guī)范,降低溝通成本,提升效率和代碼質(zhì)量。

DDD不是一種架構(gòu)思想。DDD和架構(gòu)之間是正交的關(guān)系(就是沒啥關(guān)系),DDD可以和多種架構(gòu)方式配合使用,包括n層架構(gòu),端口和連接器架構(gòu),Clean架構(gòu),微服務(wù)架構(gòu)等等。DDD是一種設(shè)計范式,主張以領(lǐng)域模型為中心驅(qū)動整個軟件的設(shè)計。在DDD中,業(yè)務(wù)分析領(lǐng)域建模是軟件開發(fā)的關(guān)鍵活動。它不關(guān)心軟件的架構(gòu)是怎樣的。隨著技術(shù)的發(fā)展,我們可能在新版本中更換軟件的架構(gòu),但是只要業(yè)務(wù)沒有變更,領(lǐng)域模型就是穩(wěn)定的,無需改動。筆者的開源小項目,已經(jīng)實現(xiàn)了對基礎(chǔ)設(shè)施的動態(tài)替換,spring 都可以換

Robert C. Martin 在《整潔架構(gòu)》一書中認為,架構(gòu)業(yè)務(wù)功能無關(guān),只聚焦于軟件質(zhì)量,尤其是內(nèi)部質(zhì)量。而DDD完全聚焦于業(yè)務(wù)功能——發(fā)現(xiàn)問題域的內(nèi)部組成、結(jié)構(gòu)、規(guī)則和機制。

在做架構(gòu)設(shè)計時,一個好的架構(gòu)應(yīng)該需要實現(xiàn)以下幾個目標:

  • 獨立于框架:架構(gòu)不應(yīng)該依賴某個外部的庫或框架,不應(yīng)該被框架的結(jié)構(gòu)所束縛。

  • 獨立于UI:前臺展示的樣式可能會隨時發(fā)生變化(今天可能是網(wǎng)頁、明天可能變成console、后天是獨立app),但是底層架構(gòu)不應(yīng)該隨之而變化。

  • 獨立于底層數(shù)據(jù)源:無論今天你用MySQL、Oracle還是MongoDB、CouchDB,甚至使用文件系統(tǒng),軟件架構(gòu)不應(yīng)該因為不同的底層數(shù)據(jù)儲存方式而產(chǎn)生巨大改變。

  • 獨立于外部依賴:無論外部依賴如何變更、升級,業(yè)務(wù)的核心邏輯不應(yīng)該隨之而大幅變化。

  • 可測試:無論外部依賴了什么數(shù)據(jù)庫、硬件、UI或者服務(wù),業(yè)務(wù)的邏輯應(yīng)該都能夠快速被驗證正確性。

策略模式與充血模型設(shè)計?

傳統(tǒng)系統(tǒng)設(shè)計,大部分從數(shù)據(jù)庫開始--自底向上的設(shè)計,這種設(shè)計會使系統(tǒng)的設(shè)計受到數(shù)據(jù)庫的影響,會有比較大的局限性,比如說:數(shù)據(jù)庫僅有數(shù)據(jù),沒有行為,而對現(xiàn)實世界的描述則會更加抽象,更加遠離業(yè)務(wù).開發(fā)團隊通過與產(chǎn)品或客戶的溝通,直接設(shè)計表模型,由于會受到溝通的效率的影響,客戶不懂技術(shù),開發(fā)從技術(shù)角度思考,整個溝通過程則會有較大的信息損失(失真),所設(shè)計出的表模型則很可能面臨后期業(yè)務(wù)變動的挑戰(zhàn).導致系統(tǒng)的成敗主要取決于架構(gòu)師在那個領(lǐng)域的業(yè)務(wù)水平而不是技術(shù)水平.

而DDD是采用自頂而下的設(shè)計,戰(zhàn)略設(shè)計時業(yè)務(wù)為王,不再受到技術(shù)的局限性(筆者認為作設(shè)計的時侯應(yīng)該把技術(shù)忘掉),設(shè)計過程中,需要業(yè)務(wù)專家(不用懂技術(shù),比如客戶和產(chǎn)品等)和開發(fā)團隊一塊僅僅從業(yè)務(wù)的角度分析需求,不要受到任何技術(shù)的局限性,采用事件風暴(類似頭腦風暴)的方式,討論整個系統(tǒng)面臨的各種用戶場景,整個系統(tǒng)提供的各個功能模塊,做業(yè)務(wù)上的歸集分類分級,在討論過程中,各個團隊(客戶 產(chǎn)品 開發(fā) 測試 運維 交付)逐漸統(tǒng)一語言(同一個限界上下文中),使得業(yè)務(wù)場景更加清晰的被描述和歸納分類出來,在整個溝通中更加有效率,更加準確,后續(xù)維護階段的持續(xù)交付迭代都將由此而獲益。其實就是共識一套術(shù)語表。當然這個術(shù)語也是可以迭代的。

DDD整潔架構(gòu)實踐

https://gitpod.io/workspaces

https://github.com/Sairyss/domain-driven-hexagon

六邊形架構(gòu)與洋蔥架構(gòu)其中心思想類似

image

縱向DGA架構(gòu)設(shè)計

DDD六邊形分層實踐

關(guān)于DDD戰(zhàn)術(shù)設(shè)計的一些細節(jié)問題

存儲庫是為聚合而服務(wù)的

存儲庫不是一個對象。它是一個程序邊界以及一個明確的約定,在其上命名方法時它需要的工作量與領(lǐng)域模型中的對象所需的工作量一樣多。你的存儲庫約定應(yīng)該是特定的以及能夠揭示意圖并對領(lǐng)域?qū)<揖哂幸饬x。

image.png

筆者與淘系技術(shù)觀點不一致, repository 面向聚合服務(wù)是可以共識的,但domain service 不允許調(diào)用repository 并沒有嚴格規(guī)定,原則上是可以調(diào)用的。包括 Value Object 中也是允許調(diào)用domain service 的。

https://cloud.tencent.com/developer/article/1861060
https://cloud.tencent.com/developer/article/1803939

我們是面向VO建模還是面向 Entity 建模?

盡量避免public setter IDDD 的作者費農(nóng)建議面向 VO建模,原因是VO是只讀的,對于復(fù)雜業(yè)務(wù)場景下不會被修改,對于上下游的業(yè)務(wù)邏輯是安全的,筆者非常認同。因為DDD的核心目標就是在解決復(fù)雜性,而復(fù)雜性的根本原因在于團隊協(xié)作與溝通共識。尤其在某一個業(yè)務(wù)場景下,多人開發(fā)時侯,可能會莫名的對下游的邏輯產(chǎn)生影響。而面向VO建??梢员苊忸愃茊栴}的產(chǎn)生。同時也是對代碼和團隊成員的限制和規(guī)范。

而相反ENTITY 是允許修改的,比如訂單的狀態(tài),很有可能被上游隊友(豬一樣的隊友還是有的)無意修改,導致線上bug 異常。

聚合是否需要單獨的類?

實踐的過程中我么是否需要為聚合建立一個單獨的類呢?答案是不需要。聚合只是一個虛擬的邊界,它并不是一個單獨的類。在實際中只需要用聚合根實體來表示這個聚合就可以了。不需要單獨的聚合類。

領(lǐng)域服務(wù)是否需要定義成接口?

大多數(shù)情況下不需要,但在領(lǐng)域服務(wù)多種實現(xiàn),或者有可能有多種實現(xiàn)的情況,需要定義接口,并在基礎(chǔ)設(shè)施層提供實現(xiàn)。iddd p244

什么時侯需要定義領(lǐng)域服務(wù)?

執(zhí)行一個顯著的業(yè)務(wù)操作過程。比如密碼加密(支持多種加密方式)
對領(lǐng)域?qū)ο筮M行轉(zhuǎn)換
以多個領(lǐng)域?qū)ο?aggrgation,entity,value object) 作為輸入進行計算,結(jié)果產(chǎn)生一個值對象。

領(lǐng)域?qū)ο蟮囊蕾嚪椒?/h2>

Domain Registry(服務(wù)工廠)
依賴注入(依賴spring)
構(gòu)造器(或直接參數(shù)傳遞)
以上三種方式并無好壞傾向,網(wǎng)上多數(shù)情況使用第三種(IDDD p246)

代碼落地

開源的
https://github.com/sparrow-os/sparrow-passport-ddd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容