主頁 > 軟體設計 > 從軟體復雜度的角度去理解DDD

從軟體復雜度的角度去理解DDD

2023-04-07 08:43:03 軟體設計

從我們作為業務開發主要的職責深入到DDD的本質是什么?復雜度應處理?規范設計怎么做?本文將全方位為大家解答,

一、作為業務開發,我們的主要的職責是什么的

 

業務開發的職責

在文章的開始我想和大家一起思考一個問題:作為一個工程開發,我們最主要的職責是什么? 
我極度認可 <<淺談什么是技術一號位>>文章的觀點 - 切實解決業務問題才是每一個工程開發最主要的職責 - 所以每個業務開發都必須要結合業務的視角去思考自己系統的建設和發展,而不是只是做一個“編程的”碼農,
這里摘錄一下文章中要點
  • 技術一號位是負責使用技術能力解決業務問題,提供穩定可靠的技術支撐;
  • 負責向業務各方提供各種必要的技術支撐,通過合理的資料分析為業務決策提供依據;
  • 通過對技術領域的積累和發展,通過業務領域的理解和落地影響業務決策;
  • 負責構建梯隊完整、能力全面、制度完善的技術團隊來支撐業務發展,
文中也提到了雖然不是每個人都負責一塊完成的業務,也不是每個人都帶領團隊,但是至少每個人都是自己所負責的那塊系統的技術一號位,

 

業務在實際開展中遇到的問題

那實際業務開展中,業務到底會遇到有哪些問題呢?我們按業務的生命周期進行切分,然后具體查看每個業務生命周期的訴求:
  • 業務啟動期:業務能力快速搭建 - 系統提供快速試錯的能力
  • 業務發展期:業務能力擴展 - 系統需要支持原來越多的業務功能
  • 業務平臺期:業務能力復制 - 系統需要支持原來越多的業務場景
  • 業務衰退期:業務能力創新 - 系統提高生產力延長業務的生命周期

圖片

我們技術要做的事情是:在業務驗證沒有問題的情況下,如果盡可能的延長業務的發展和平臺期,讓業務獲取的利益最大化,所以為了支持業務的發展,業務的本身的功能支持訴求以及業務對技術的要求也會越來多,在這種情況下考驗軟體開發人員的一個非常關鍵的能力就是: 軟體復雜度的控制的能力

 

軟體復雜度

軟體復雜度其實是一種多維度的概念,其可能來源于多個方面,前阿里資深技術專家李運華在他的《從0開始學架構的》課程中從6個方面闡述了軟體復雜度【2】,列舉如下:
  • 高性能
    • 單機性能
    • 集群性能
  • 高可用

    • 計算高可用
    • 存盤高可用
  • 可擴展性

  • 低成本
  • 安全
  • 規模
    • 業務規模
    • 系統物理規模

二、DDD的本質是什么

DDD本質上我認為就是一種減低軟體復雜度的手段, 其推薦的方法論可以適用于上面包括了業務規模,可擴展性兩個維度的復雜度應對,其實業務規模的復雜度的處理包括了對可擴展性的支持,
DDD實施給系統之后,我們依然需要關注系統其它的復雜度,這里列舉一些示例措施:
  • 容量規劃
  • 架構設計
  • 資料庫設計
  • 快取設計
  • 框架選型
  • 發布方案
  • 資料遷移、同步方案
  • 分庫分表方案
  • 回滾方案
  • 高并發解決方案
  • 一致性選型
  • 性能壓測方案
  • 監控報警方案
那么我們進一步對業務規模的復雜度進行拆解,又分為下面兩類:
1、領域復雜度
  • 領域模型描述問題域的準確性

2、技術實作的復雜性

代碼沒有按照業務系結的”分析模型”去編碼,軟體變成一個大泥潭
  • 軟體的可擴展性較差
  • 軟體變成面向程序
  • 分層不合理
  • 沒有規范

那DDD是如何處理上面提到的軟體復雜度的?

  • 提供了一個領域劃分的方法:讓軟體系統產生邊界,
  • 提供一個一系列的戰略模式:限界背景關系的映射,分層架構等,
  • 提供一個一系列的戰術模式:如何規劃領域層 內部

DDD不是什么?

  • 不光光只是一種編程方法
  • 不光光只是一種架構風格
  • 不具體指導如何具體建模

三、復雜度處理-領域模型描述問題域的準確性

DDD的原名是模型驅動的設計方法:通過領域模型(Domain Model)捕捉領域知識,使用領域模型構造更易維護的軟體,

 

合理性證明

圖片
DDD的核心思想,大家都清楚,就是分析模型要和代碼模型保持一致, 
那么如果不保持一致到底會產生什么樣的負面影響
圖片
如果技術實作和業務實作不在用一水平線上,那技術模型的行進路線只會考慮劈開技術障礙并且可能會撞在未來的業務障礙的墻上,這樣就很容易出現,業務持續演進等技術想實作的時候,卻發現當前的實作依賴于“業務不會這樣發展”的假設上,這也是為什么會出現現在眾多業務需求,技術無法實作或者是需要花大量時間去實作的原因,
但是如果技術和業務通過統一語言打破知識的壁壘保持一致,那么如果后面技術遇到問題即是業務碰到的問題,業務人員需求的變更和迭代會自然而然的幫助技術同學越過一些門檻,也就是說業務方與技術方參與到對方的作業中,就在雙方之間帶來了更好的協同,形成1+1>2的功效,

 

什么是問題域

根據百度百科的解釋【3】 在軟體工程中,問題域是指待開發系統的應用領域,即在客觀世界中由該系統處理的業務范圍
圖片
那么問題域內的組成是什么呢?就是我們的域模型, 
這里直接摘抄一段前阿里P10"阿白"在阿里內部發表的域模型的觀點:
域模型(domain model)英文又稱為問題域模型(problem space model),維基百科(Wikipedia)對它的定義是” A conceptual model of all the topics related to a specific problem” 可以翻譯成:“域模型是針對某個特定問題的所有相關方面的抽象模型”,這個定義有幾個要點:第一是“特定問題”, 也即是說域模型是針對性某個問題域而言的, 脫離的這個特定問題,域模型的構建其實不存在一個最優或者是最合理的構建,第二是抽象, 域模型是一個抽象模型, 不是對某個問題的各個相關方面的一個映射, 也不是解決方案的構建, 
圖片

 

如何實作問題域的分析

在 DDD 中,Eric Evans 提倡出一種叫做知識消化(Knowledge Crunching)的方法幫助我們去提煉領域模型,簡單來說就是五個步驟:
  • 關聯模型與軟體實作;
  • 基于模型提取統一語言;
  • 開發富含知識的模型;
  • 精煉模型;
  • 頭腦風暴與試驗,

圖片

開發人員和業務專家在一起通過一個個業務用例仔細討論應用程式的應用場景,從而使得業務人員深刻理解業務知識,開發人員和業務人員就重要的業務概念建立起統一的語言,開發人員將這些概念根據業務用例的背景關系抽象出模型,并且這些模型將會最終成為最終軟體實作中的領域模型,隨后隨著更多的業務用例的輸入,開發人員和業務人員會逐漸對已經構建的模型進行精化,并且也會用新的用例去檢驗之前構建模型的合法性和適用性,
DDD在這一步其實沒有給出詳實標準的如何建模的方法,畢竟建模還是來自于每個人的世界觀,其程序還是傾向于經驗的,但是還是有不少人總結一些標準的建模方法論例如:

1 四色原型法  

http://apframework.com/2020/03/22/ddd-color/

2 用例分析法 

https://baike.baidu.com/item/%E7%94%A8%E4%BE%8B%E5%88%86%E6%9E%90/2859078?fr=aladdin

 

問題域的拆分

大家應該發現上面的知識消化的流程是一個非常耗時和復雜耗腦力的程序, 涉及到產品,業務,技術等多方團隊, 所以為了讓有限的資源投入到最最核心的子域,我們需要對問題域進行這份,把重點的精力放到最核心的領域上,
核心領域一定是業務價值最高的,而非技術難度最高或者是基礎設施框架部分, 
要切分問題域,首先需要了解問題域的種類:

1 通用域: 非應用獨有的,多個應用都會有的功能,例如發送郵件,觸達等

2 核心域:和競爭對手區別開來的區域,或者是在市場上被賦予了競爭優勢的區域,

3 支撐子域:其余的區域
如何確定核心域,這里有幾個提示:
  • 系統哪部分最難用
  • 手動處理程序阻止了他們進行了根據創造性, 有附加值的作業
  • 哪些修改能提高收益
  • 哪些修改能提高運營效率

取哪些提示,取決于業務系統的性質,

那如何決定支撐子域/通用子域,以及支撐子域/通用子域的切分呢?
目前在我查閱的資料中,還暫時沒有人提及到具體的操作方法,感覺主要還是依靠經驗主義在做劃分,我個人總結了一個方法,主要就是就是關注業務的核心物體和核心流程,以核心物體和核心流程作為切分支撐子域的基礎,
核心物體:核心物體是存在于核心流程中,對核心流程的決策和扭轉可能起到關鍵的作用,有的時候業務上為了能讓核心物體在業務流程中起到更大或者更高效的作用,會添加一些讓核心物體更好服務于業務流程一些業務功能,從而使業務物體從整體上看變得相對復雜,這個時候我們應該以核心物體為基礎進行切割,把所有和核心物體CRUD相關的操作還有讓其變得更高效的業務功能劃分為單獨的一個領域,
核心流程: 當某個業務流程足夠復雜也可以當成一個子域,
在實作領域驅動設計【4】書中,提到了為在線拍賣網站系統劃分問題域的一個例子,我們以此來驗證上面等構想
圖片
劃分子域
圖片
  • 賣家 + 會員身份:這兩者都是核心物體,網站可能為了讓促進會員能夠多參與拍賣可能提供了分層,或者積分等工功能,網站為了能讓賣家能夠更加提供更加有拍賣價值或者是轉化率高的品類可能為賣家提供了資料分析等業務功能, 
  • 名冊:這也就是核心物體,網站會對名冊提供一系列拍賣相關的功能,例如倒計時,一口價等,所以也需要形成一個領域, 
  • 拍賣:網站最核心的業務流程,核心域無疑,
  • 爭議解決:買賣家的售后沖突解決流程向來很復雜,所以會獨立成為一個域無疑,

四、復雜度處理-進一步降低問題域的復雜度-限界背景關系

 

限界背景關系的誕生背景

一般情況下,一個復雜系統由一系列的模型來表示解答域, 理想狀態是一個子域一個模型,但是有些當業務需要且系統復雜的時候,一個模型可能被多個域共享,這個時候這個模型的概念可能變得不清楚,因此為了保護這些模型概念的完整性, 清晰的定義模型的責任邊界很重要,
實作領域驅動設計【4】書中舉了下面這個例子:
圖片
為了維護模型的概念的完整性,最直觀的方法就是為這個模型化一個邊界,e.g. 這個商品所表現的意思就是履約的時候用到的"商品",而不是下單的時候的"商品",只要有一個這樣的邊界定義,系統就會但是出現多個邊界,畢竟"商品"在不同業務背景關系中有不同的含義, 例如庫存域的貨品,物流域的運輸品, 價格域的商品等等,這樣的一個邊界就是DDD的“限界背景關系”, 
限界背景關系給人直觀的感受其實和子域很像,我很早以前曾讀過一些關于微服務的書籍,也提到過要把DDD中的限界背景關系作為微服務劃分的重要依據,這里其實就給我很大的疑惑:
1 限界背景關系到底是怎么劃分的?我們劃分限界背景關系難道真的是用一個基礎概念,然后找這個基礎概念不同的“背景關系”嗎?
2 限界背景關系和子域到底區別是啥?

 

限界背景關系的本質

DDD理論中提到了DDD的四個邊界 
圖片
所以在DDD中是把限界背景關系作為某個子域的內部模塊的劃分,其實無論是子域的劃分,限界背景關系的識別,和聚合的劃分他們的本質是一樣的,他們都是對復雜問題的分解之后,然后歸類分組,只不過“聚合”面向的是領域層內部,“領域”劃分面向的是業務問題域,而“限界背景關系”面向的是解答域,但是我跟傾向于把限界背景關系理解為更加深一層次的業務問題域的劃分,而不是面向的解答域, 
如果這樣看的話,那么其實就可以回答上面的疑問, 領域和限界背景關系沒有本質的區別,就像樹的父節點和位元組點一樣都是樹節點,而限界背景關系的劃分完全可以使用子域劃分的理論,(可以回顧下上面問題域拆分的段落)

 

背景關系映射

背景關系的映射是什么, 簡單來說就是描述不同背景關系之間的關系的描述,舉個例子
圖片
DDD對于限界背景關系直接提煉了幾種方式,這里這邊阿里內部文章《領域驅動設計:軟體復雜性應對之道》解釋的比較好,描述如下:
shared kernel
共享內核 shared kernel :通常是共享核心領域或者是一組通用子領域,
圖片

customer/supplier

客戶/供應商關系 customer/supplier:上下游關系,不同客戶需要協商來平衡,上游團隊需要有自動測驗套件,
圖片

conformist

跟隨者模式 conformist:單方面跟隨模式,上游的設計質量較好,容易兼容,可以采用嚴格遵循上游團隊的模型,
圖片

anticorruption layer

防腐層 anticorruption layer:防腐層、隔離層,使用 facade or adapter 等模式,可以減少其它系統變動對本系統的影響,
圖片

separate way

各行其道 separate way:宣告一個與其它背景關系毫無關聯的 bounded context,使開發人員能夠在這個小范圍內找到簡單、專用的解決方案,
圖片

open host service

開放主機服務 open host service:開放子系統供其他系統訪問,其核心思想是開放出一個標準的各個領域都認可的協議,減輕各個領域實施ACL的負擔和成本,
圖片

published language

共享語言 published language:把一個良好檔案化、能夠表達領域資訊的共享語言作為公共的通信媒介,必要時在其它資訊與該語言之間進行轉換,
在當前電商領域的范疇,目前我個人覺得只有ACL,Seperate Way, publish language 有比較好可行性,其他的關系都不是很靠譜:
  • shared kernel:如果使用共享二方庫,誰來維護這個二方庫,如何防止在不同背景關系使用不同kernal版本所帶來的問題, 
如果一定能保證shared kernel的維護在一個團隊內,且所有使用shared kernel版本一定能保持一致, 那是可以使用的, 
  • customer/supplier:我曾經因為匯率包升級而去重構一個應用,因為匯率包變更太大,且應用沒有防腐層,所以不論從開發還是測驗都是非常痛苦的程序,
  • conformist:和customer/supplier類似, 但是在互聯網領域沒有靠譜的設計, 只有有人維護和沒有人維護的設計,conformist從長期來看其實就是customer/supplier,
  • Open Host Service 沒有任何一個領域保證自己的介面一定不會變,就算不會,其他領域的同學會相信嗎,他們會忍住不用ACL嗎?如果他們用ACL,OHS的意義何在?
  • publish language 目前阿里內部MTOP,TOP等協議正是使用這樣的協議,

另外限界背景關系之間真的能夠隨便無規則無條件的互相依賴,互相呼叫嗎?在下面的章節將會解釋論述,

五、復雜度處理 - 分層不合理

架構分層主要的作用就是關注點隔離,如果和今天的話題聯系起來就是領域模型和技術的關注點隔離(領域和存盤,領域和展示),

 

傳統的三層架構

圖片
這種傳統架構的缺點
1、業務邏輯層和資料訪問層有明顯的耦合, 
2、沒有領域的概念,所有的邏輯沉淀到service中,
所以傳統架構只能針對小型的,沒有過多的業務邏輯場景,由于這種架構能夠保有領域能力的沉淀,所以在現在電商業務場景基本不會被使用,

 

六邊形架構

Alistair Cockburn在 2005 年時演示了 六邊形架構
圖片
圖片
從六邊形架構開始,其強調了領域模型,并且確立了領域模型的核心位置,以及其不應該依賴于其他的層次,六邊形架構也強化了配接器的概念,其還把配接器分類為input配接器,和output配接器,所有input配接器用于對接不用的外部請求形式, 所有output配接器用于對外部的依賴 (e.g. 資料庫, 外部服務,記憶體呼叫等)
這種結構模式樹立了以領域模型為核心的先河,但是其忽略了在領域層中跨模型業務邏輯的實作方式-領域服務的沉淀,這也間接導致了其需要強化應用層,并且通過應用層和output配接器的聯合去完成一些可以應該在領域層應該完成的事情, 

 

洋蔥架構

圖片
洋蔥架構的提出更加進化了一步,推出了域服務層,并且支持域服務層是支持了那些需要多個領域物體聯合中作用的領域邏輯. 其層次由外向內依次是領域模型,領域服務,應用服務和外層的基礎設施和用戶終端,其依賴的關系也只能是由外向內. 在洋蔥結構中其把存盤層,檔案系統和網路服務放到了基礎設施層,由于基礎設施和用戶終端一樣在最外層,所以洋蔥架構也提倡用依賴倒置來解決應用邏輯和基礎設施的耦合問題,
洋蔥架構的架構圖從其依賴順序上來看,其依賴應用層必須先依賴域服務層,再依賴域模型層, 這樣很容易造成領域模型的邏輯外泄到領域服務層,造成領域模型變成貧血模型,

 

DDD 架構

 

圖片
DDD的架構大家都非常熟悉了,領域服務和領域模型都歸屬域領域層,適配層依賴域應用層,應用層依賴域領域層,也可以直接直接呼叫基礎設施層(大多數是查詢場景),領域層理論上不依賴于任何層次,其通過依賴倒置和基礎設施層產生關聯,在DDD架構中,應用層是可以通過直接訪問聚合根(某個物體類),并進行方法的執行和操作的,應用層也可以直接訪問基礎設施層,可以看出DDD的架構其實更加的貼切實際一些, 
圖片
上面這張圖也很好的闡述了DDD各個架構層次依賴的關系,

 

CQRS

 

很多情況產品構建出來的資料展示,需要橫跨幾個領域的資料的支撐,也就是我們日常構建的大寬表,在這種情況使用CQRS模式可以完美解決這個問題,其主導視圖模型和領域模型分開,讓領域模型更加專注業務邏輯,流程和規則而非業務視圖, 
圖片
CQRS的思想很簡單,就是把服務中對資料的更新操作(Command)和讀取操作(Query)分離, 一部分邏輯只處理和資料更新有關的業務,另外一部分只處理和資料讀取有關的邏輯,這種處理方式,可以讓我們辛苦構建的領域模型不被業務中所需要的這類視圖需求所干擾, 
CQRS 的兩種實作方式
基于event- sourcing
圖片
不基于event - sourcing
圖片
以上的圖片摘自于文章《CQRS模式及其應用》

 

我們團隊里面的架構實踐

 

在我們自己的應用中我們構建了基于COLA【5】規范的層次架構,如下圖: 
圖片
我們也對自己的架構定義的一些額外的規范:

1、依賴關系(除了依賴倒置)只能是從上當下;

2、同層之間永遠不能互相依賴;

3、如果同層之間需要互相用到對方的服務,那么就需要下沉出一層,例如在上圖中,我們的業務層就分為了兩層 "Executor"層次和 “Handler”層此, Handler層次用來保存業務的一些通用邏輯,

六、復雜度 - 軟體變成一個大泥潭

從這一章節開始介紹DDD的"戰術模式",也就是向大家介紹DDD是如何構建和組織自己領域層的,值得一提的是在DDD中,領域的劃分, 領域層次的建立, 領域之間關系的建立我們一般叫做DDD的"戰略模式",而此章節提到的值物件,物體,域服務,工廠,repository, 聚合/聚合根, 領域事件等都是DDD的戰術模式,戰略模式的重要性是要遠大于DDD的戰術模式的,我們如果在領域劃分,領域通信協議,分層方面沒有大的問題, 那么即使再糟糕系統整體也還是可控的, 
在領域層面, DDD通過聚合/聚合根的概念來劃分單個領域中的類似于類集合的邊界,從而降低單個領域層的復雜度,DDD通過物體,值物件,領域服務,repository, factory 來規劃集合內部的類組織, 另外DDD也通過領域事件來處理領域之間的互動,來匹配異步和需要解耦的業務場景,

 

物體

 

當我們需要考慮一個物件的個性特征,獲取需要區分物件的時候,就需要引入物體,一般我們發現物體概念,是在和業務產品人員或者領域專家討論發現的那些需要有唯一標示性或者生命周期連續性很重要的時候,
舉個例子加入用戶需要預定酒店,如果領域專家說了我們定了A酒店了,就不能定B酒店了,哪怕A,B其他的屬性完全一樣,從領域專家扣中我們可以識別出酒店是有唯一標示性的,且哪怕A,B屬性一樣,也不能認為A,B 是一樣的,這也說明了酒店的唯一性不是從屬性來的,這兩點我們可以推斷酒店是一個物體,唯一標示性可以是現實有意義的,例如工商注冊號,也可以無現實意義,例如資料庫主鍵, 

物體建模的注意點

1、為物體分配唯一識別符號
  • 現實意義識別符號
  • 人工生成的標識
    • 自增
    • guid/uuid/
    • 資料庫主鍵
    • 自定義sequence

2、驗證和不變行

物體必須自己負責自己保持自己狀態的合法性 (validation) 和不變性(Invariants),他們的區別是合法性是根據背景關系的,而不變性是不用考慮背景關系且必須正確的,例如酒店必須有房間這個就是不變性,而酒店的營業時間就是validation. 一般使用規則和規約模式來實作validation和invariants,

規模模式: 

https://baijiahao.baidu.com/s?id=1717403406288752234&wfr=spider&for=pc
3、聚焦在行為,而不是屬性狀態
不要暴露屬性給外面,如果外面得到屬性,很可能就自己實作了一些領域邏輯,那么領域邏輯就外漏了, 
4、把一些行為邏輯下方到值物件中
需要警惕物體邏輯膨脹,從而混繞了物體所要表達的概念, 
例如預定是一個物體, 現在要加上邏輯預定的天數不能小于N天,這個時候我們可以為Booking 抽象出 Stay 物件,讓Stay物件去管理規則邏輯,而不是讓預定這個物體去做,讓預定只關注預定, 
5、不要為世界建模
不要過度設計,只要滿足需求就好,不要讓技術需求污染領域設計,除非真的萬不得已,
6、分布式的設計
不需要讓領域概念橫跨多個bounded context, 如果我們域模型所涉及的概念橫跨了,我們就需要用兩種設計方法.
  1. 只是用id參考
  2. value objects

 

值物件

 

什么時候需要使用到值物件?

  • 概念需要凸顯的時候, 
e.g. 拍賣系統的能夠一口價獲取拍賣的價格, 就算是我們用一個int 就能表示也需要用類來凸顯概念
public class WinningBid
{
...
public int Price { get; private set; }
...
}
  • 表述一個描述性的,但是沒有物體編號的概念的時候, 

值物件的特征

  • 無標識:他們只是標識物件的屬性,
  • 基于屬性的相等性: 所有的屬性值相等即值物件相等
  • 富含行為:值物件實作業務概念的抽象,其也有自己的行為
  • 內聚:將不同的相關屬性組成一個概念整體,例如Money, 是由一個long 和一個currency組成的
  • 不變性:值物件是不變的物件,如果需要改變屬性,那最好是建立一個新的物件并且進行值物件替換,如果一定是需要改變,那就需要考慮設定為值物件是否合理,
不變性是值物件非常重要的一個屬性,是可以保障值物件不會被"壞味道"代碼侵入的一個原則之一, 例如如果一個值物件引入了另外一個類實體, 另外一個值物件也引入了相同的類實體, 如果值物件允許改動,當一個值物件對這個類實體的內容進行修改,勢必會影響另外一個值物件, 所以最安全的方式還是通過物件替換的方式,

 

域服務 

 

什么時候用域服務

發現和多個物體相關聯,但是放入任何一個單獨的物體都不適合,這個適合用域服務.

域服務應該包含什么內容

域服務應該包含業務/系統流程和業務規則,不應該包含技術的元素在內,技術的元素都應該在業務服務(Application Service)中實作,

應用服務與領域服務的區別:

一些可以在網上搜索到的老生常談:
  • 應用服務里不要處理業務邏輯,只在領域服務里處理業務邏輯,(如何判斷某段邏輯是否是業務邏輯?)
  • 領域服務掌握領域知識,而應用服務只是對領域服務的編排,
  • 應用服務是領域服務的客戶方,也就是說應用服務會呼叫領域服務里的方法,
  • 當領域中的某個操作程序不屬于物體或者值物件的職責時,需要將個操作放在領域服務中,而且確保領域服務是無狀態的(這句話很有意思,也就是說領域服務中不應該有任何記錄狀態的行為,在任何情況下呼叫這個服務,它都不會有副作用,也就是說它是個純記憶體操作),
  • 領域服務中包含的是業務邏輯,而應用服務關注的應該是安全和事務等非業務邏輯,
  • 對事務的管理絕對不能放在領域服務層,事務管理需要放在應用服務層,因為和領域模型相關的操作的粒度都很細,無法用于事務管理,而且領域模型也不應該意識到事務的存在,
  • 通常的可以放在應用服務中的邏輯有:引數驗證、錯誤處理、監控日志、事務處理、認證與授權,
除了第一條之外,上面的條例只是舉出了應用服務與領域服務兩者非常易于告之的差別,但是會有一些“業務邏輯”比較難以取舍, 例如
例如轉賬操作:

A ->B

A.accountDecrease(10);

B.accountIncrease(10);
我們在現在可以非常肯定的說上面的轉賬一定在領域服務,因為"轉賬"就是一個領域概念,但是如果假設世面上所有的銀行以前只有存錢和取錢兩種功能,"轉賬"是一個新概念和業務的時候,技術就沒有那么容易判別轉賬是一個臨時性的一次性的需求,還是會長久發展,這個時候技術有兩個選擇:
1、構建轉賬域服務,讓應用服務呼叫域服務
2、讓應用服務獲取A,B的物體,然后在應用層直接呼叫方法,在應用層做事務保持一致性,
如果域能力在其他團隊手里,我相信大多數的團隊會使用第二種,那遇到這種情況我們到底應該怎么辦?我個人的意見和阿里前技術高級專家張建飛在他的文章《一文教會你如何寫復雜業務代碼》的觀點保持一致:

圖片

我們在新邏輯出現難以判斷的時候優先講能力放入到app層,如果我們發現會有第二個業務場景使用到了相同的能力,就需要考慮是否應該把此能力下發到領域層以增強內聚性和復用性,

 

工廠

 

  • 只負責復雜邏輯物件的構建,讓構建邏輯中心化
  • 減少外部物件對構建物件內部變數的理解
  • 工廠方式不是在任何構建物件的時候使用,一定用在物件構建邏輯復雜,有子依賴或者是有invariant規則的場景, 

 

Repository

 

reponsitory主要用來處理集合根的存盤和獲取的,提供一個facade介面且是面向domain層的,是domain model和data model的橋梁,其最大的作用就是通過反向依賴的方式充分隔離資料層和領域層,Repository最常見的用法是被applicaiton service層去使用獲取聚合根,
在repository實作中,我們一般會有下面的一些邏輯:
  • uniqe ID 的生成
  • 資料庫的操作
  • 資料模型到領域模型的相互
  • 橫跨多個資料模型構建出一個物體模型,

repository的反模式

  • 定義出比較通用化的介面, e.g.
List<Customer> findBy(CusomterQuery query)
  • 使用了延遲加載,延遲加載就是設計錯誤的標志, 有可能說明我們聚合的邊界不催, 
  • 不要為了報表的訴求使用reponsitory, 領域的case和業務報告很不一樣,可能需要多個聚合的資料,這種情況可以考慮用一個離線的store去做,和其他的讀服務去做,不見得一定需要用領域的Repository模式,

 

領域事件

 

領域事件所想要解決的問題其實和metaQ訊息機制想要解決的問題一致,都是跨領域驅動型業務邏輯實作的最佳方法,讓領域和領域之前解耦,
領域事件消費教科書的說法是可以在領域層, 也可以在應用服務層, 但是我覺得領域訊息如果用metaQ是這樣的訊息中間件去實作的話,那用領域層和應用服務層去消費就不是很方便,有可能破壞一些分層原則,所以我個人傾向于在adapter層去承接消費,統一化掉,
當前可以實作領域事件和消費比較方便的工具有:
  • google guava - EventBus
  • COLA框架 - 事件支持

 

聚合/聚合根

 

聚合是什么?

其實聚合的原理和領域劃分,限界背景關系劃分的原理是一致的,都是為了通過歸類分組的方式讓整個系統宏觀上 N * N 的關系復雜度減低為 T * T 的復雜度, 
T遠小于N, 
聚合前: 
圖片
聚合后
圖片

如何劃分聚合

1、根據業務規則和不變數來決定,例如 customer 聚合是有一套業務規則來維持的,例如信用卡要存在,必須先有一個customer, 有customer 必須有address, address必須有code.
2、強關聯的物件應該放在一塊,什么是強關聯,那就是必須生命周期是一樣的,例如customer和creditcard在,電商網站中,如果customer被洗掉了,那么他的信用卡也應該被設定為失效的,而訂單和客戶不一樣,客戶下了個訂單,然后客戶注銷,但是訂單還是一直存在的,所以customer 和 creditcard 在一個聚合中, 而用戶和訂單則不在,訂單里面可以有客戶的ID或者是一個值物件, 
3、靈活設定:有些可以根據業務情況,進行可以靈活的設定,下面列舉一個論壇系統,帖子和回復聚合思考的例子:
大家都知道一個帖子有多個回復,沒有帖子,回復就沒有意義;所以很多人就會認為帖子應該聚合回復;但實際上不需要這樣,如果你這樣做了,那對于一個論壇來說,同一個帖子被多個人同時回復的可能性是非常高的,那這樣的話,多個人同時回復一個帖子,就會導致多個人同時修改同一個帖子物件,那就導致大家都回復不了,因為會有并發沖突或者資料庫事務的等待超時,因為大家都在修改同一個帖子聚合根;實際上如果我們從業務規則的角度去思考一下,那可以發現,其實帖子和回復之間,只有一個簡單的規則,那就是回復一旦被創建,那他所對應的帖子不能被修改即可;這樣的話,要實作這個規則其實很簡單,把回復作為聚合根,然后把帖子傳入回復聚合根的建構式,然后回復保存帖子ID,然后回復將帖子ID設定為不允許外部修改(private set;即可),這樣我們就實作了這個業務規則,同時還做到了多人同時推一個帖子回復時,不會對同一個帖子物件就并發修改,而是每個回復都是并行的往資料庫插入一潭訓復記錄即可,-- 摘自阿里內部檔案<<關于DDD領域驅動設計中聚合設計的一些思考>>

聚合設計的原則

  1. 聚合是用來封裝真正的不變性,而不是簡單的將物件組合在一起;
  2. 聚合應盡量設計的小;
  3. 聚合之間的關聯通過ID,而不是物件參考;
  4. 聚合內強一致性,聚合之間最終一致性;

什么是聚合根

聚合根就是聚合的入口,聚合外部只能通過聚合根和聚合內部通信,由于聚合外部只能通過聚合根和聚合內部通信, 這也就意味著外部不能操作除聚合根以外的任何類進行資料庫操作,因為這樣有可能會導致破壞業務的規則,舉個例子,一個汽車四個輪子,如果我們用 Repo 直接操作輪子,對輪子采取delete,而這個時候汽車物件的狀態卻可能是 "running".

如何選出聚合根

1、聚合根一定至少有一個對應的datastore 

2、聚合根一定更夠完全描述一組后者多組業務規則(invariant),絕對不會存在一個業務規則需要多個聚合根聯合作用才能做判斷的,

3、聚合根一定有自己的獨立的生命周期, 

七、規范設計

專案/應用的規范設計的長期價值一定是不可忽視的,規范就相當于一個架構內部組件的收納的容器,架構指定了這些組件在邏輯上的組織形式,但而規范則是則是妥妥的物理組織形式,如果沒有合理清晰的規范設計,專案很快就會因為個人開發習慣的不同,導致物理結構混亂,給接下來的應用/專案的維護和擴展產生影響,
規范設計總共分為兩類:

放對位置

  • 程式架構目錄

貼好標簽

  • 類名約定
  • 方法名約定
  • 錯誤碼約定
  • Domain Event約定
  • 測驗約定

下面是我們小團隊參考了阿里COLA框架建議的命名規范做出的開發規范:

 

程式架構組織

 

層次
包名
功能
Adapter層
web
處理頁面請求的Controller
mtop
處理mtop的請求
hsf
處理hsf的請求
scheduler
處理定時器的請求
message
處理訊息的請求
App層
executor
處理request,包括command和query
convertor
包含轉換層類的目錄
interceptor
包含攔截器目錄
extpoint
擴展點目錄
extention
擴展實作目錄
handler
app 層的一些中間處理邏輯
Domain層
model
領域模型
 
ability
領域能力,包括DomainService
gateway
領域網關,解耦利器
extpoint
擴展點目錄
extention
擴展實作目錄
Infra層
gatewayimpl
網關實作
mapper
ibatis資料庫映射
config
配置資訊
Client SDK
api
服務對外透出的API
dto
服務對外的DTO

 

類命名規范

 

種類
物件
示例
API class
增刪改服務入參
XXXCmd. 
e.g. MetricsRegistrationCmd
查詢服務入參
XXXQry. 
e.g. ListAllMetricsQry
API service
XXXServiceI.java
e.g. MoMetricsAdminServiceI
出參
如果是無需回傳回傳使用Cola框架的Response, 
如果是單個概念回傳使用Cola框架的SingleResponse, 
如果是多個概念回傳使用Cola框架的MultiResponse. 
如果概念是某個明確的物件,例如MultiResponse<MoMetricsCO>
如果是復合物件, 使用XXXResult 例如
MultiResponse<ListMetricsValuesResult>
領域層
物體
XXXE.java
值物件
XXXV.java
工廠
XXXFactory.java
Reponsitory
XXXRepository.java
域服務
XXXDomainService.java
防腐服務
XXXGateway.java
列舉
XXXEnum.java
應用層
命令執行器
XXXXCmdExe.java
查詢執行器
XXXXQueryExe.java
攔截器
XXXInterceptor.java
擴展介面
XXXExtPt.java
擴展實作
XXXExt.java
e.g. 
#{Biz}#{userCase}#{scenario}Ext.java
轉換器
XXXConvertor.java
基礎設施層
存盤的物件
XXXPO.java
倉庫實作
XXXReponsitoryImpl.java
防腐服務實作
XXXGatewayImpl.java
公共層
工具類
XXXUtil.java
ThreadLocal
XXXThreadlocal.java
其他
介面
XXXI.java
Spring配置類
XXXConfiguration.java
配置屬性聚合類
XXXProperties.java

 

錯誤碼規范 

 

圖片

 

擴展規范

 

使用COLA的擴展框架去實作,COLA的擴展框架的功能不在贅訴,這里討論的是COLA框架的BizScenario的劃分, 

BizScenario

private String bizId = "#defaultBizId#"; 

private String useCase = "#defaultUseCase#";

private String scenario = "#defaultScenario#";

bizId: 業務,對應某個業務訴求, e.g. 新品范訓,

useCase: 業務用例, e.g. 策略定義

scenario:用例下的某個場景,e.g. 定義商家事件任務, 定義小二事件任務
全域業務通用實作
@Extention
某項業務通用實作
@Extention(bizId=XXX)
某項業務,某個用例的通用實作
@Extention(bizId=XXX, userCase=YYY)
某個具體實作
@Extention(bizId=XXX, userCase=YYY,scenario=ZZZ)

參考:

【1】「技術人生」專題第1篇:什么是技術一號位? :https://www.sohu.com/na/465299450_612370

【2】https://time.geekbang.org/column/article/6605:https://time.geekbang.org/column/article/6605

【3】https://baike.baidu.com/item/%E9%97%AE%E9%A2%98%E5%9F%9F/7181289?fr=aladdin

【4】實作領域驅動設計. 電子工業出版社:https://book.douban.com/subject/25844633/

【5】https://github.com/alibaba/COLA
作者|臺城

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Understanding-DDD-from-the-perspective-of-software-complexity.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/549358.html

標籤:其他

上一篇:【過濾器設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

下一篇:【過濾器設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more