DDD隨談

wu發表於2021-08-10

  前言  

  最近再次拜讀了Eric的奠基之作【Domain-Driven Design –Tackling Complexity in the Heart of Software】,還有Vernon的【Inplementing Domain-Driven Design】,雖然是java版本,但並不影響閱讀,畢竟設計思想是通用的,結合個人使用DDD的一些經驗,做個隨談。

  在我看來,DDD絕非什麼標新立異的的思想,更多的是軟體發展的自然結果。就像20世紀六七十年代出現的軟體危機後,物件導向程式設計被搬上了舞臺;瀑布式開發在快速發展的網際網路時代,敏捷成為了它的救贖;而DDD更多的是對傳統的以資料為中心的開發習慣的一種反思。

  如果你關心軟體工藝,而不僅僅是coding,那麼領域驅動設計便是非常重要的一項技能。

  防軟體退化利器    

  隨著軟體業的快速發展,軟體規模越來越大,生命週期也越來越長,推倒重新開發的風險越來越大。這時,軟體團隊急需在較低成本的狀態下持續維護一個系統很多年。

  然而,事與願違,隨著時間的推移,程式越來越亂,維護成本越來越高,軟體退化成了無數軟體團隊的噩夢。

  我們的軟體總是經歷著這樣的輪迴,軟體設計質量最高的時候是第一次設計的那個版本,當第一個版本設計上線以後就開始各種需求疊加和變更,這常常又會打亂原有的設計。

  特別是當我們許多團隊都在實踐敏捷開發,但敏捷開發的落地對開發團隊的設計能力、設計質量,提出了非常高的要求。因為每個敏捷週期,都是在對上一個版本的更新。如果設計質量跟不上,更新速度越快,程式碼退化就越快。程式碼質量下降了,還能敏捷得起來嗎?所以我們如何在快速進行迭代交付的同時,又能保持著高質量的軟體設計呢?    

  現狀  

  長久以來,我們程式設計師都是很好的技術型思考者,我們總是擅長從技術的角度來解決專案問題。但是,一個軟體系統是否真正可用,是通過它所提供的業務價值體現出來的。

  遺憾的是,我們更多的開發人員,對DDD的實踐都是停留在"地面"上。

  過度拘泥於實現細節,而不是從一開始就居高臨下俯視我們的軟體,會錯失讓我們在天空概覽的機會。

  我們開發人員總是在技術層面追求著高內聚,低耦合的完美設計,對不起,如果你不懂DDD在戰略層面在業務系統存在的意義,憑著戰術層面的指導,是實現不了這樣的完美設計的。  

   意義

  有沒有什麼方式可以讓我們保持軟體的質量呢?有,那就是領域驅動設計!

  首先DDD並不是關於技術的,而是關於討論,聆聽,理解和發現業務價值。因此,與其每天鑽在那些永遠也學不完的技術中,何不將我們的關注點向軟體系統所提供的業務價值方向思考,這也正是DDD所試圖解決的問題。

  DDD的核心理念中,是劃分為戰略設計(天空)以及戰術設計(地面)兩部分的。  

  戰略設計主要從高層“俯視”我們的軟體系統,幫助我們精準地劃分領域,明確各個領域的邊界以及處理各個領域之間的關係;而戰術設計則從技術實現的層面教會我們如何具體地實施DDD。

  可以看出,戰略設計在整個DDD落地過程中,是佔核心地位的,它給我們提供了高屋建瓴的寬闊視野。  

  DDD的戰略設計幫助我們清晰的劃分不同的業務系統和各自的業務關注點,這樣可以有效的保護各自系統並實現高內聚低耦合的設計原則。   

   困境

  DDD理念面世這麼多年了,為什麼業界內還是很少實際的案例呢?個人認為有幾點原因:

  1. DDD提倡的是基於現實世界的現實行為進行建模,這就限制了案例的產生,畢竟這是業界的核心業務,不太可能作為案例披露出來的。

  2. 沒有好的領域專家,DDD一直強調領域專家的重要性,在我們這個行業,業務和技術都深入的,太少了,這個行業充斥著急功近利,沒有多少人真正是對某個行業進行鑽研透徹的。

  3. 概念繁多,對人的要求極高,特別是抽象能力,容易讓直性思維的人繞進去,學習使用成本會比較高。

  4. 忽視戰略層面的意義,導致很多案例胎死腹中。

   即使是會面臨不少的困境,但DDD仍然是諸多公司追捧的寵兒,除了它能使我們開發者提高抽象能力之外,DDD它本身的作用是簡單化,而不是複雜化。

  在使用DDD時,我們應該採用最簡單的方式進行復雜領域建模,而不是使問題變得更加複雜。    

  突破的關鍵點

  當我們在實施過程中面臨著各種各樣的問題時,有哪些策略是可以指導我們進行專項突破的點嗎?  

  有的,從戰略設計先入手。

  上面提到了,我們使用DDD,更多的是從戰術設計這個”地面“著手,所以會出現了DDD-Lite的情況。而這是本末倒置的,DDD首先讓我們關注的不是技術,而是業務語言。  

  領域劃分/限界上下文

   既然是領域驅動設計,那麼我們的設計重點肯定是在領域了,以及領域模型的正確設計了。

  首先領域並不是很高深的詞彙,它是問題域集合的代名詞。我們確定我們產品所屬的領域後,所面臨的問題是確定的,比如說我們是一個電商系統,它是屬於電商領域,那麼會遇到使用者,訂單,購物車,商品,交易,物流等明確的問題集合需要我們解決,這些問題域是確切的。

  在領域驅動設計中,強調對於子域的正確劃分,即使是在日常開發中,我們通常會也將一個大型的軟體系統拆分成若干個子系統。這種劃分有可能是基於架構方面的考慮,也有可能是基於業務的。

  在DDD中,我們對系統的劃分是基於領域的,也明確是基於業務的。即我們可以基於領域專家的領域業務知識,將整個系統劃分成許多相對獨立的業務場景(子域),然後在一個一個的子域中進行領域模型分析與建模。

  然後我們很快就發現了問題,哪些概念應該建模在哪些子域裡面?我們可能會發現一個領域模型建模在子系統A中是可以的,而建模在子系統B中似乎也合乎情理。

  如何能正確定於模型含義呢?限界上下文!

  限界上下文是一個顯式的邊界,領域模型便存在這個邊界內,建立邊界的原因在於,每一個模型概念,包括它的屬性和操作,在這個邊界內都是具有特殊含義的。  

  

   從這裡可以看出,如果光憑名字,我們是無法區分兩個賬戶的意思的,只有通過它們所在的限界上下文,我們才能看出它們之間的區別。

  限界上下文是用來為領域提供語境的,它保證在領域之內的通用語言、領域模型有一個確切的含義,沒有二義性。

  行為豐富的模型

  我一直認為,DDD中的領域模型才是一個真正意義上的OOP,它所推崇的充血模型,是對映著我們真實世界的真實行為。我們平時口中所謂的OOP,實體只是單純的資料載體,沒有更多的功能,DDD推薦的領域物件,是跟我們現實生活中的概念是一致的,有具體的行為,是一個行為飽滿的物件,這樣才是DDD的威力所在,也是我們實現高內聚低耦合的途徑。

  領域模型即業務。

  從DDD的名稱我們就可以看出,領域驅動設計中,領域模型是最核心的點所在,所以在設計得到模型後,DDD要求我們在程式碼中無偏差地實現模型,也就是所謂模型驅動開發(Model-Driven-Design, MDD)。

  行為豐富的領域模型,才是DDD最大的威力,能設計出行為豐富並且涵蓋諸多現實業務的模型,就是消化領域知識的最好體現。

  由於這種模型是我們現實世界的真實描述,鑑於我們真實世界的知識跨度的速度(參考地心說的統治時間),是可以維持軟體到一定的生命週期的。這種模型的行為豐滿,符合真實世界的認知,且業務純淨,減少了犯錯的可能性。

  要建立行為飽滿的領域物件並不難,首先出發點是認真思考我們真實世界的可靠行為,把這些行為提煉到模型中,再次我們需要轉變一下思維,將領域物件當做是服務的提供方,而不是資料容器,結合真實世界多思考一個領域物件能夠提供哪些行為,而不是資料。

  DDD和微服務

  這幾年當DDD再次映入我的眼簾,是微服務的興起,DDD已經面世好多年了,隨著微服務的興起,DDD重新活躍在我們的眼中,或者說,微服務的興起,也依賴了DDD的鋪墊。它們是相輔相成的。

  微服務的特點

  微服務的一個核心點就在於服務的劃分,整個系統被被分成了很多個輕量的模組,它的一個原則就是劃分出來的模組在於“專”(小並不是微服務的重要的考慮因素,具體參看【微服務架構 - 正確的開始】),即每個服務的鬆散耦合上,也就是我們常說的高內聚,低耦合。

  無獨有偶,DDD也是基於高內聚,低耦合的思想來指導我們如何劃分正確的子域。

  限界上下文/上下文對映圖

  我們上面講了,在限界上下文中,其中的領域模型都是高內聚的存在,它們的關聯性是非常強的,它們只會在同一個原因的條件下進行軟體變化,所以通常情況下,一個限界上下文下的子域是可以設計為一個微服務應用程式,而這個微服務的邊界,就是這個限界上下文,服務間的關係,就是上下文對映圖。

  在微服務中藉助DDD的思想劃分服務是大概這麼一個過程:

  • 按照限界上下文進行微服務的拆分,按照上下文對映圖定義各微服務之間的介面與呼叫關係;
  • 通過限界上下文的劃分,在各自的子域內進行建模,並基於充血模型或者貧血模型落地各個微服務的領域模型;
  • 按照領域模型設計各個微服務的資料庫;
  • 將以上的設計最終落實到微服務之間的呼叫、領域事件的通知。

  資料一致性

  在微服務中,會面臨著我們分散式系統的常見問題,其一就是事務的一致性。在DDD中,領域事件便可以用於處理這些問題,此時最終一致性取代了事務一致性,通過領域事件的方式達到各個元件之間的資料一致性。

  後續

  洋洋灑灑的聊了些個人對DDD的一些看法,其中的部分概念會後續在這個系列的博文章節裡繼續探討。

  DDD也不是"銀彈"

  正如微服務架構中的“微服務不是銀彈”,領域驅動設計也會面臨同樣的問題。作為架構師,我始終認為我們在任何的情況下對於任何的特定技術,都可以活學活用,所以個人使用DDD的一個理念的是,不要為了DDD而去DDD。

  領域驅動設計作為物件導向程式設計的高階方法論,它其中的很多設計是非常美妙以及契合實際的,然而所謂設計,是要以我們的團隊的知識、經驗和智慧,全面充分的考慮各種內外因素後,在設計方案中作出合理的選擇的過程。

  我們的目標是什麼?是追求完美的DDD嗎?不是,我們的目標是把系統做得更健壯,賦予產品強大且持久的生命力,所以我們在真正的使用過程中,其實是藉助了DDD很多的設計思想來指導我們的系統設計。

  沒人在乎你是否是一個純正的DDD,老闆以及使用者注重的,是你所使用的技術帶來的業務價值。

  所以在使用領域驅動設計時,並不代表整個系統的方方面面都必須遵從領域驅動設計的原則,需要根據實際情況,讓適合的部分使用領域驅動設計,讓不適合的部分使用程式導向的設計。   

  DDD落地是一個深入瞭解業務的過程

  DDD 的真諦是領域建模,即深入理解業務。我們不可能一步到位深刻理解業務,它是一個逐步深入的過程。只有深入理解業務,將對業務的深入理解設計到領域模型中,設計出來的軟體才更加專業。因此,基於每個限界上下文進行領域建模,不斷地將每個功能加入模型中,落地每個微服務的設計。

  當業務越來越複雜,理解越來越深入的時候,我們要適時地調整原有的模型,就能適應新的功能。正因為 DDD 就是要應對的是軟體的這樣的不確定性的複雜,才會通過結合現實世界的理解,領域建模去抽象複雜業務,讓複雜業務得到簡化,從而簡化軟體的設計,使設計始終處於高質量的水準上。

  因此,我們學習 DDD,首先就要把設計做到位,準確理解那些領域,限界上下文,聚合、倉庫、領域事件等基礎概念,並在設計實戰中做出正確的設計。


   DDD中有很多概念是相對來說是比較抽象的,特別是對習慣邏輯思維的程式設計師來說,摳概念是比較痛苦的,所以很多技術人員在初學DDD時,更多的是關注戰術層面的設計,然而過度地強調DDD的技術性將使我們錯過由戰略設計帶來的好處,畢竟戰略層面可以升個級為整個軟體的業務架構,這個架構是支撐軟體生命週期重要的依據。    

  記住,領域模型的設計並不會一蹴而就,我們需要反覆研究領域知識,不斷重構模型,才能將領域中的重要概念提煉成簡單而清晰的模型。

相關文章