
序:
《阿甘正傳》中,阿甘開始了不停地跑步,一段時間后,后面就有了很多追隨者一起跑,他們為什么跑哪?
類似地,一開始我也不知道DDD是什么,但當發現大家都在提DDD、都在學DDD的時候,我也像跟跑者一樣不由自主地加入了前行:既然有大牛提出了DDD,既然那么多人趨之若鶩,那么肯定有可取的地方,
然而,有一天,阿甘停止了跑步,他不想跑了,追隨者遇到了一個問題:我們還要跑么?當我們在學習DDD的程序中,感覺學而不得的時候,可能也會問:我們還要學么?這的確引人深思,
本文基于作業經驗,嘗試談談對DDD的一些理解,希望能夠更好地探尋學習DDD的意義,
無論做業務,還是做平臺、中臺,大家常常會被交錯復雜的業務邏輯、晦澀耦合的業務代碼搞得心力憔悴,我想,大家對DDD的追求,也是對輕松支撐業務發展的訴求,在探尋有沒有合適的理論可以改善現狀,畢竟,美好生活,共同向往,
我們選擇各種框架、進行各種組織設計,核心是為了提高生產效率,但如果業務邏輯都是 case by case 地進行實作、缺少復用,那么研發成本是非常高的、投入周期也會非常長,
為了增加復用、縮短業務的落地時間,就需要很多通用的能力、產品,在我們的交付程序中,主要有兩個層次:

分層支撐框架
這樣的層次,看上去很美好:在起步階段,由于缺少歷史包袱,的確可以提升一定的生產效率,這是能力本身的收益,但是,越往后,隨著業務接入的增多,業務之間開始互相影響,研發的阻力也越來越大,
研發效能降低的重要原因在于:更多的時候,我們還是按照“業務能跑起來,怎么快怎么來“的邏輯去做相關作業,遇水搭橋,遇山開洞,然后直達目的地,進行資訊的傳達、資料庫欄位的操作,
這樣的程序,違背了我們”希望通過業務場景,豐富平臺能力,同時保證內核干凈“的初衷,能力應該是基于相對多的用例、相對完善的思考進行抽象,是橫向統一看,有更深刻的理解,但是垂直的交付,讓我們更加縱向地處理問題,往往只是“窺探”了鏈路,在交付時長和業務節點的限制下,很難想得更加全面、深刻,難以做出更通用的設計,
那么,為什么關注DDD?如果說DDD直擊了軟體復雜度的核心——“問題域”,那可能還是比較抽象,具體來說,因為這的確符合我們追求的價值觀【提升長期的生產效率】:
●細分領域,培養專業的人、事:因為DDD的核心是要求讓各個領域做好理解和封裝,把一個業務需求拆解、安放在各自合理的地方,通過這樣的分解與沉淀,保證了領域的輸入,能夠得到長期可持續的發展,形成競爭力,
●機制保障,不依賴易變的事物:DDD其實在總結很多通用的技巧和經驗,能夠讓這樣的實施更具有確定性,無論是聚合根對領域物體的管控能力、限界背景關系的互動策略、領域內核的抽象地位...等等,一旦選擇尊奉,確定下來,就能夠落在代碼結構、組織關系、團隊檔案中,形成共識,不會因為人員等因素的變化而劇烈波動,
對DDD的關注,可以類比于我們對“工匠精神”的關注,對DDD的重視,也是我們對業務理解的重視,貼近業務,更要理解業務,不僅要理解業務,更需要理解大多數的業務,這樣的追求,讓我們往上看了一個層次,回歸了最本質的問題:我們要解決什么問題?如何能夠解決得更好?
不知道大家是否有這樣困惑:DDD的學習程序好像是”大海撈針“的程序,即使能夠撈到點東西,使用起來,還是會有種“東施效顰”的感覺,并不是很自然,為什么學習DDD那么困難呢?
叔孫武叔語大夫于朝曰:“子貢賢于仲尼,” 子服景伯以告子貢,子貢曰:“譬之宮墻,賜之墻也及肩,窺見室家之好;夫子之墻數仞,不得其門而入,不見宗廟之美、百官之富,得其門者或寡矣,夫子之云不亦宜乎!”
正如要感受到孔子達到的境界,自己的學問也需要有一定的積累,我們要感受到DDD的力量,自己本身就要成長到一定程度(如:經歷了一些成功或者失敗的設計,有自己的經驗或者教訓),才能形成共鳴和認同,
作業中,的確很少看到DDD的最佳實踐,在復雜的業務面前,誰也沒有勇氣說,哪個軟體結構是理想的設計:
-
因為這不是一個確定性的問題分解,你的設計會被放在顯微鏡下研究,總能找到各種反例,
-
而且,我們深知,最佳的實踐,一定是做得足夠的“軟”,對擴展留有設計,能夠隨著業務發展而迭代,不是一個靜態的結果,
因此,打開學習的大門不是幾個案例就能一蹴而就的,需要結合我們自己的作業,慢慢累積、體會,
-
抽象的理論,覺得空洞:當我們看到”抽象的DDD定義與策略“的時候,可能會發問:這也算DDD?不就是一些軟體設計的共識,然后強加了一些名詞定義,有些策略與我們手上的系統也并不匹配,
無論往抽象看,還是往具體實作看,都很難找到令人信服的理論與實踐(能夠有確定性的落地能力),因為這不像23個設計模式那樣,可以通過N個模版就能涵蓋大多數的模式,
為什么不能產生特定的模式呢?可以結合下圖進一步來看:
-
抽象理論:如同抽象的介面一樣,"DDD理解"最上面的學習主要是理論定義,比如:聚合根、值物件、資源庫、核心域、支撐域等各種定義,這些是易于理解掌握的,
-
通用實踐:如同相對具體的抽象類一樣,"DDD理解"中間層次是一些通用原則和技巧,比如:背景關系的映射策略、架構的選擇等,這些因素是確定的,但需要自主進行取舍與選擇,并且需要與時俱進,增量的部分需要你自己學習補充,
-
具體實踐:如同具體的類實體一樣,"DDD理解"中下面層次是一系列的具體實踐,結合各自的業務場景,進行了不同因素的設計、取舍與補充,因為涉及的選項較多,造成最終的選擇結果往往是發散的,令人感覺“千人千面”,
因此,由于問題的抽象層次較高,各種策略的不確定性較高,很難在DDD中產生像”23個設計模式“那樣精煉的模式,一定要有的話,也是一系列的模式,比較發散,

因此,我們漸漸明白:DDD面向的是軟體的“軟”,涵蓋方方面面,DDD的深入理解,需要“千錘百煉”后,才能明白那些深入簡出的建議,才能體會那句“師傅領進門,修行靠個人”的真言,才能感受到“眾里尋他千百度,那人卻在燈火闌珊處”的美妙瞬間,
雖然DDD本身的實踐可能千人千面,但是一些核心主題的思考應該是聚焦的,這些高頻主題的理解,能讓我們更好地進行設計,討論的性價比也是較高的,接下來,會基于“6大設計原則”(solid 原則)為引子,去看看DDD中的一些關鍵理解,
單一職責是說:一個類應該只有一個發生變化的原因,職責的單一,可以更好地內聚,減少耦合,方便演化,
DDD里面的領域劃分可以類比思考,對領域的劃分,無論是按領域物體,還是按照功能模塊,還是按照服務等劃分,其實都想盡量保證領域的正交,能夠獨立演化和發展,
Single Responsibility Principle:單一職責原則
領域怎么切分比較合適?剛進入業務平臺的時候,了解到領域切分是按“一個或多個物體物件”的邊界來切分,這的確比較合理,因為領域的核心職責就是對領域物體進行管理,但這是果,還是因?在切分的時候,是因為我們有了對領域的判斷,所以某些物體被分在一起比較合適;還是因為某些物體有明顯邊界,所以可以形成一個領域?就比如下面的圖:

這的確引入深思,切分比較容易的時候,往往是因為已經有了行業的標準(如:電商系統有訂單、支付、物流、庫存等領域是比較合理的),那行業的標準來自哪里?是來自于演化:
所以,領域的演化和劃分,很類似“啟發式演算法”(一個基于直觀或經驗構造的演算法,在可接受的花費下給出待解決組合優化問題每一個實體的一個可行解):
-
初始化:按照的經驗初步的劃分,也可以是行業標準(沒有行業標準的時候,就只能靠經驗了),
-
花費評價:生產、交付程序的人力成本度量,關注理解成本、開發效率、系統穩定性、運維成本等因素,
-
更優解:在業務發展程序中,計算花費評價,分析影響評價的“好因素”、“壞因素”,進行進一步調整,
-
調整的內容:其實是匹配生產關系,
-
調整的原則:追求職責的內聚,精細化分工,
-
不斷調整的原因:業務在發展,內聚的標準需也要與時俱進,
此外,從關聯的角度看,往往我們看組織架構,就能看到領域的邊界,核心原因還是組織架構也是要適應生產關系,follow更優解的結構,是相輔相成的,也就能互相窺探,
開閉原則是說:軟體中的物件(類、模塊、函式等)應該對于擴展是開放的,但是對于修改是封閉的,也就是說,對擴展區塊是要有設計的,擴展的部分不應該影響穩定邏輯,
在DDD中,物體的行為,在保證對外封閉的情況下,也是需要考慮擴展能力的,

Open Closed Principle:開閉原則
在剛開始學習DDD的時候,我們可能會強行把一些邏輯放到物體中,進行控制和收斂,但后面隨著業務的變化,會發現在物體中承擔行為邏輯很難受:
-
場景較多:很多邏輯并不是正交的,不是 if 這樣 else 那樣的,充滿著交集與疊加,
拋棄 POJO 的get、set,走向物體的豐富行為,讓我們撰寫代碼更加困難了么?其實,我們的煩惱來自于,太關注物體行為的收口,忽略了擴展的設計:
但是,要突破這一個困境,能夠在物體行為中設計擴展,其實要有這樣的認同:要往上看一個層次,就是物體行為的表達,不一定只有一個類完成,可以通過策略模式等方式的路由,由一個模塊中的一些類進行完成,只要對外有封裝和管控其實就可以了,
突破一個類的限制,走向更多的類的協作設計,也是我們進階的方向,
里氏代換原則是說:任何基類可以出現的地方,子類一定可以出現,講究的是合理的抽象和復用,引人深思的一個例子是:正方形是特殊的長方形,正方形如果作為長方形的子類,那么當設定長度的時候就會出現沖突,長方形的長和寬可以獨立設定,正方形的長和寬是有約束的,使用繼承的關系就比較別扭,

Liskov Substitution Principle:里氏替換原則
在原來的分層的架構中,資料庫等存盤能力作為一種底層基礎設施,是被視為穩定的下層服務的,但在實際的交付程序中,往往要遇到不同場景:
-
本地部署:線下零售交易為了服務穩定性,期望可以具備本地保存資料的能力,
-
云上產品:售賣給外部企業的交易產品,成本的要求也不盡相同,期望在云上采購不同的存盤服務,
這些場景,讓大家逐漸深信:當面向更廣闊的市場,基礎設施也是充滿著不確定性,需要具備可替換的能力,
在具體實作的程序中,并不是每個領域都會獨立部署,有些領域因為組織、性能的因素會一起部署,往往這些領域的代碼也是在一個專案模塊中的,出于橫向效率的考慮,會設計統一的存盤框架,
不同設施的存盤能力不同,但整個存盤流程又是類似的(協議轉換,存盤陳述句生成、執行與事務,回傳結果),這樣在不同存盤能力的程序復用方式上需要進行取舍(資料庫、Tair等是分開抽象還是統一抽象):
-
代價:但大多數人要做擴展的時候,會感到很多不解,有很多本不需要的適配,充滿迷惑,
我想,“基于不同的存盤能力,設計不同的模版框架” 應該是首選,大一統的抽象,開始時,人力成本可能低一點,但因為抽象層次較高,在理解與維護上將是一個“人力成本黑洞”,隨著時間的推移,會降低整體收益,長期看是得不償失的,反之,不同的模版復用,最終可以獲得更好的整體收益,
迪米特法則,又稱最小知識原則,是說:一個軟體物體應當盡可能少的與其他物體發生相互作用,應該和一些“關鍵類”進行溝通,
DDD里面,領域間的協作,也需要相關的規劃和設計,如果對領域之間的相互呼叫不做管理,那么鏈路關系會膨脹到難以理解的地步,

Law of Demeter:迪米特法則
設計模式中,無論是中介者模式,還是外觀模式,都希望通過集中管理,讓多對多的復雜關系,簡化為多對一、一對多這樣易于理解的結構,類似的,在領域協作與對外交付的程序中,往往可以增加一個協調層,去串聯各個域的互動,這樣即可以降低各域的協作成本,也可以降低外部的理解成本,有更好的研發體驗,
協調層該如何產生?好比上課:雖然老師可以教,但是老師不在時,可以指定學生代理上課,學生雖然可以干,但教學技巧并不熟練,自己本身還有學習的職責,角色也很尷尬,下面將討論一下協調層和域的角色關系,
比較值得討論的是交易里面“交易域”、“訂單域”這樣的概念:
沒有物體,為什么會有“交易域”一說,本人是這么理解的:在交易流程等可以強管控的情況下,把交易的API服務當做域服務(如:下單),“交易域”在邏輯上是有邊界、可以成立的,但本質還是在管理訂單,靠訂單域成為了域,同時想沉淀協調能力,
那么,如果訂單的模型的管理不給交易管理呢,就是本人一直想的問題:如果沒有自己的資料庫物體,只有記憶體模型,純粹靠對下游業務活動理解、資料流轉的理解,能否成為一個域?
協調者的角色,想要成為一個比較公認的“域”,必定要自己持有資料模型,或者,借助基礎域的一些資料模型,并享有管理的權限,
無論是域想承擔協調者的角色,還是協調者想發展成一個域,其實都不太符合職責單一的邏輯,但是這樣“兼職”的現象卻時常發生,核心還是開發人員角色的重疊,
既然協調層不太適合從域中選出,也不太適合成為一個域,那么介于業務活動、各個域能力之間的協調部分,應該稱之為什么呢?目前看“商業能力”、“解決方案”這樣的詞匯都是比較合適的,
介面隔離原則是說:客戶端不應該依賴它不需要的介面,一個類對另一個類的依賴應該建立在最小的介面上,
DDD里面除了領域建設的學習,也需要關注應用層如何更好地承接業務請求,并研究業務邏輯分割的依據,

Interface Segregation Principle:介面隔離原則
之前在業務部門做業務的時候,并沒有業務活動、流程等相關概念,往往是基于業務需求寫業務腳本,經驗的多少會影響代碼的優雅,但除了經驗,大家并沒有比較好的結構框架、原則,去承接應用層的各種業務邏輯,因此也充滿疑惑:
-
對外服務介面應該如何切分?
-
類似服務之間是否可以共用流程?
-
業務執行程序如何進一步結構化切分?
-
沒有規范的結果是,往往各有各的看法,誰都想立一套結構,誰也不服誰起的一套,各有各的代碼區域,
現在作業中,因為在平臺,平臺思考和治理的時間也比較久了,也有比較穩定的共識,整體的設計,在業務入口和業務入口之間、業務入口和穩定邏輯之間,預留了空間和擴展能力去承接場景化的邏輯,結構也比較確定:
-
入口按業務活動切分:服務入口按照業務活動擴展,核心理解并關注用例,什么角色要干什么事情,
-
流程獨立承接與編排:業務活動在到達復用層(如:領域服務、外部服務、業務擴展)前保持獨立,各自編排,
-
借助流程檔案編排執行程序:將執行程序切分為執行節點,節點的切分可以以“功能點”、”涉及的域“、“涉及的物體“等為依據,節點間的共同能力下沉到基礎能力中,
分而治之,縮小大家的關注點,更好地切分協作,這樣的確很容易理解并接受,但是要保證合理的切分,還是要有統一的共識和原則,
往往一致的形成,不是先一致共識,再切分,而是初步溝通后就嘗試切分,再review達成一致,在曲折中前進,如果大家的看法、沖突較大,那么這個共識達成的程序就相對較慢,好在,這種切分也不是時常發生,也就大需求、大重構的時候,此時,預留的研討時間、開發資源也是充足的,
依賴倒置原則:六邊形架構
依賴倒置原則是說:程式要依賴于抽象介面,不要依賴于具體實作,
DDD里面提到的六邊形架構,也是進一步提升了抽象內核的地位,把領域建設作為架構的核心目標,

Dependence Inversion Principle:依賴倒置原則
-
原來以分層架構為主:講究按層次去看,盡量將能力下層,進行更多工具復用,積累的是通用組件,
-
現在以領域為中心:講究按抽象層次去看,盡量將理解融入到領域核心,進行更多“理解”復用,積累的是業務知識,
這樣的轉變,讓我們有意識地將“領域理解”作為核心,形成行業競爭力,把“知識”作為資產進行售賣,
為了保證領域內核的抽象,需要定義好領域內核的邊界,有兩類介面:
-
對上游提供的能力:通過介面宣告,說明能承擔的職責,在領域內部進行實作支撐,
-
對下游的能力依賴:外部服務、業務擴展定制、存盤服務都可以作為下游服務看待,通過介面宣告服務依賴,
可以看到,領域內核與外部之間通過介面進行解耦,對于更基礎的服務,會被視為和業務入口一樣的外部埠,屬于應用層,比如,存盤服務:
-
原來更多的是基礎能力:資料框架 + DO,不需要理解轉換,轉換在上游完成,DO 也會作為核心模型被上游使用,在采用遵循模型策略的時候,上游完全使用 DO 作為核心物件進行流轉,
-
現在可以理解為“業務組件“:需要實作領域的存盤介面,承擔協議轉換,將領域物件轉換為資料存盤物件DO,DO也不會被領域直接理解,需要轉換為領域物件再往外透,被領域內核定義了表現,
這樣的架構,奠定了領域理解的抽象核心地位,讓研發同學更加注重對業務問題的思考,建設更多“有血有肉”、“貼近業務核心問題”的軟體,不僅僅是“基礎骨架”,讓我們更加走近客戶的價值,是應對軟體復雜性程序中,大家比較認可的方向,
對DDD的追求,來自我們渴望優雅地解決各種業務問題,希望有一套框架可以引導我們去分解問題,得到穩定、高效的生產效率,
但是這好比對“永動機”的追求,是一個難以有肯定答案的程序,能夠解決軟體復雜性的方案,必定是結合相關場景并且不斷演化的,單純追求DDD是得不到“銀彈”的,
不過正如對“永動機”的研究,能讓我們關注能量的轉換程序,可以引導我們制造出更加高效的能源機器,對DDD的研究與追捧,能夠讓我們更加關注對業務的深刻理解,可以引領我們寫出更加易于擴展的代碼實作,
我想,正是“業務的不斷發展”、“軟體的復雜性”的存在,才讓編程充滿了挑戰,才讓大家對框架的研究充滿熱情,這何嘗不是一個美妙的事情那~
本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/One-article-explores-the-significance-of-learning-DDD.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/545001.html
標籤:架構設計
上一篇:Java面向物件進階第一天
下一篇:我用ChatGPT做直播技術選型,卷死了同事