DDD已經火了很久,目前在很多項目上都有所應用,而這次是我第一次參加DDD相關的培訓,對我來說神秘的DDD一層一層揭開了它的面紗。
背景
DDD為什么這么火,它終歸是解決了軟件設計與開發過程中的一些痛點的。
培訓過程中有一句話“任何人類的設計都會隨著時間腐化,軟件系統也不例外”,可見在軟件開發過程中,一開始做的軟件設計,隨著時間的流逝、需求的變更,慢慢變得不再合理,反而成為了負擔。
那么在軟件系統整個開發過程存在著的挑戰有哪些呢?
- 在軟件開發中存在著不確定性,而這個不確定性會一直貫穿軟件工程的生命周期中。
- 沒有所謂的“銀彈”,能夠用來解決軟件的復雜度問題。
- 在軟件工程里溝通非常重要,如何讓整個團隊都保持著信任和良好的溝通就成了要思考的問題。
了解了背景和痛點以后再來看DDD可以為我們帶來什么改變,讓它成為了“潮流”。
先來了解一下DDD是什么
Domain-driven design,領域驅動設計,它是一種處理高度復雜域的設計思想。主要是圍繞業務概念來構建領域模型,從而來控制住業務的復雜性,使得系統不斷演進時仍然可以保持敏捷。
在傳統的方法里,業務架構和系統架構是綁定在一起的,所以當我們去響應業務變化調整業務架構時,系統架構也會隨之改變。
而對于DDD來說,主要聚焦在領域和領域邏輯,所以這種設計方式下的系統架構是向領域模型靠攏的,領域模型內部封裝了數據和?為的對象,所以當業務發生變化時,不會影響整個系統架構。那么是由誰來識別領域從而做出設計的呢?是技術專家和領域專家一起合作,缺一不可,這樣才能不斷切?問題領域的核心。
那么在做領域驅動設計的時候有哪些原則呢?下面就來介紹一下。
DDD三個原則
上面我們說過領域驅動設計主要的焦點是領域和領域邏輯,所以第一個原則就是
1.聚焦核心領域
我們要先了解什么是領域(domain),看下面的圖表可以知道領域中分為核心領域和子域,而子域又分為?撐?域和通用子域。
圖表中具體的一個業務領域或子域其實指的是一個業務范圍以及在業務范圍內進?的活動。一個業務領域或子域可以包括多個業務能?,?個業務能力一般對應著?個服務。
那么圖表中的幾類領域有什么區別呢?
- 核?子域一般是指業務成功的主要促成因素,是項目的核心競爭?;
- 通??域雖然不是核心,但被整個業務系統所使用;
- ?撐?域雖然也不是核心,也不被整個系統使用,但是是完成業務的必要能力。
我之前老是分不清通用子域和支撐子域,因為核心子域很好區分,但是這兩種子域都是“輔助”型的,就傻傻分不清楚了。后來想想顧名思義,通用子域,重點在于通用,如果這個子域被很多個業務系統調用,它就是通用子域。如果一個子域單純的只是作為某一個業務領域的“附屬”,沒有通用的定義,它就是支撐子域。而一個支撐子域隨著業務需求的發展,也有可能搖身一變成為通用子域。
2.通過協作迭代式探索模型
在傳統模式中,是基于所有未來的假設需求而做的龐大的設計。例如,基于造車的需求,做好一個龐大的設計后,就會開始先造輪子、再造車架子、一步一步往車的模型上走。
而迭代式探索模型是根據需求的變化來不斷演進模型。在迭代式探索模型中,同樣是造車的需求,就會先造一個滑板車、自行車、摩托車、慢慢演進成車。在實際場景中,其實很難一開始就訂好整個項目的所有需求,一般都是有個長期的目標或者明確的藍圖,然后對于近一個月的需求比較明確,對于近2周的需求非常明確。所以真實實踐過程中,迭代探索模型比較適合敏捷開發流程。
3.使用統一語言
在介紹DDD的時候說過,領域驅動的設計是由技術專家和領域專家一起合作完成的。所以通過良好的溝通協作,統一語言就很重要。
對于技術人員和業務人員來說,他們各自有著自己的語言,例如技術人員大部分表達的是技術術語、技術設計模式、關于技術方面的設計,而業務人員大部分表達的是業務術語、業務需求,所以需要技術人員和業務人員一起來統一語言,避免分歧。而這個被統一后的語言就是領域模型術語。
了解DDD的三個原則以后,再來看看如何在實施過程中遵循這三個原則。
DDD的實施步驟
1.團隊消化業務知識,建議統一語言。
一開始就是技術專家和領域專家一起協作,雙方都明確的了解業務知識,在整個討論過程中建立一個統一的領域模型術語,在之后設計模型時使用。
2.初步提煉領域模型,識別領域模型。
技術專家和領域專家把領域模型術語都明確出來以后,識別出哪些術語是一個領域的,每一個領域內部承載的職責,初步提煉出領域模型。
3.分解領域模型復雜度,劃分子域和上下文。
在初步提煉出領域模型后,需要明確的知道每個領域之間的關系、交互等等,從而來劃分出子域和上下文。
4.細分領域模型內部元素,識別實體值對象和聚合根。
前三個步驟都是在一層一層往上抽象,第四步回歸到領域內部,識別出領域內的實體對象和聚合根有哪些,這個就涉及到比較細節的業務功能。
由上面4個步驟,我們知道如何去實施DDD,但是卻不知道,每一步的細節是如何做到的,例如怎么去找出領域模型術語?怎么確認哪些術語是一個領域?怎么歸納子域和上下文等等。這個時候就可以考慮用到事件風暴工作坊(Event Storming)了,具體的實施方式請看下文。
Event Storming
Event Storming是?種領域建模的實踐,是?種快速探索復雜業務領域的?法。
Event Storming最開始是由Alberto Brandolini開發的一種方式。在2015年11月進入了ThoughtWorks技術雷達,這也意味著Event Storming這種方式被大家所認可,有著它的實際價值。
- Powerful: Event Storming可以讓實踐者在數?時內理解復雜的業務模型,是一種快速探索復雜業務的很好的方式。
- Engaging: Event Storming的參與人員就是帶著問題的人和擁有答案的人,他們共聚一堂來構建模型。
- Efficient: 它跟DDD的實現模型?度一致,并能快速發現聚合和 Bounded Context [1]
- Easy: 在 Event Storming 中的標記都很簡單,沒有復雜的UML
- Fun:這整個過程非常的愉快,不會讓人覺得枯燥乏味。
[1] Bounded Context在DDD是分而治之的意思,就是不要把一整個很大的系統看成一個context,而是依據「某種方式」把整個大系統分成若干個有界限的context。
了解 Event Storming 以后,主要是了解怎么進行 Event Storming,看看怎么使用這種方式來構建領域模型。
1.確定參與人員
業務?員,領域專家,技術人員,架構師,測試?員等關鍵?色。
2.確定產品愿景和價值定位
產品愿景是對產品的頂層價值設計,主要是對產品的?標?戶、核?價值、差異化競爭點、痛點等策略層面的信息在團隊內部達成共識。
可以套用模板:
對于 "目標用戶群體"
它們想 "訴求或痛點"
這個 “產品名稱”
是一個 “產品特征”
它可以“不可抗拒的優點”
不同于 “其他競品”
我們的產品 “核心的差異化競爭力”
最后得到的結果是:
3.事件風暴
事件風暴顧名思義就是對于“事件”的頭腦風暴,用來梳理業務流程、建立領域模型、劃分邊界。
3.1設計好業務場景
事件風暴第一件事就是設計好要討論的業務場景。根據之前定下的產品愿景與價值定位,設計出關鍵場景,找出業務的起點與終點。
因為往往整個產品的功能是很復雜的,包含了很多業務場景,但是在做事件風暴時我們主要聚焦在關鍵的業務場景,然后找出業務起點和業務終點,確定業務范圍。例如在我們這個例子中原本是一個玩具網上商城,但是它的核心競爭力是玩具置換,所以我們設計的關鍵場景就是玩具置換的流程,業務的起點是發布玩具,終點是玩具置換完成。
3.2識別領域事件
識別出業務范圍內發生的事件,一個領域事件指的是業務上真實發生的事情,必須對業務有價值,有助于形成完整的閉環。
事件都是有時間順序的。列舉事件可以用“名詞+動詞”這樣的形式,例如“玩具已發布”。
3.3事件排序
將上面識別出來的事件貼在白板上,并且每個事件從左到右按時間順序排列,同時間段發生的要保證相對順序。例如下圖是整理后的玩具置換業務模塊的事件圖。
4.命令風暴
命令指的是產?事件的領域?為或領域活動,命令一般由三種方式觸發:(1)用戶從UI界?進行的操作 (2)外部系統觸發(3)定時任務
4.1識別命令
命令風暴這個過程中,需要識別出命令有哪些,把它貼在事件的左邊,如果有命令產生了多個事件,就用包含箭頭的虛線連接起來。如下圖中的藍色小方塊就是命令。
在上圖中“確認意向”這個命令會觸發“意向單已確認”、“置換單已創建”、“玩具已下架”等三個事件,所以用包含箭頭的藍色虛線連接起來。
4. 2識別出觸發命令的?色
找出命令對應觸發它的角色并標識出來。如下圖中的黃色小方塊,就是觸發這個命令的角色。例如玩具置換發起方會發起一個意向,然后意向單就會被創建。
5.尋找領域模型
在前面的步驟中,已經分析出領域事件,主要是根據事件來尋找領域模型。在上面事件的模板都是“名詞+動詞”的方式,所以很容易識別出事件中有業務含義的名詞,需要確保事件中的名詞代表的業務概念清晰完整并且沒有歧義。
上面的事件例子歸納總結后,發現領域模型只有“玩具”、“意向單”、“置換單”、“物流單”。
6.尋找聚合
聚合是一組相關領域模型的集合,是為了保證邊界內領域對象的業務不變性。
它通過定義對象之間清晰的所屬關系和邊界來確保關聯關系緊密的領域模型能夠內聚在一起,從?避免錯綜復雜的對象關系網形成。聚合內部的領域對象是具有?致的?命周期的。
如何來尋找聚合?是通過前?的領域模型來確定的:
- 領域模型是否可以被獨?訪問,如果可以被獨?訪問就是一個聚合。
- 如果領域模型不能被獨?訪問,就應該屬于它依賴的聚合
下圖就是整理出來的聚合,不巧,剛好每一個領域模型都是一個聚合,然后把命令放在聚合的左面,事件放在聚合的右面。
7.提煉子域
子域的概念,我們在解釋DDD的部分的時候說過,簡單來說?域就指的是一個業務范圍以及在業務范圍內進?的活動。
我們需要回顧產品愿景與價值定位,找出用戶痛點和產品的核心價值。然后根據前?步識別的領域模型圖來討論子域,并確定映射關系,最后驗證我們的?案是否解決并覆蓋全部問題空間。
例如在我們的例子中:核?子域就是置換子域,支撐域是商品子域,通用域是物流子域。關于支撐域和通用域也在DDD部分解釋過,一個子域的定位完全根據你制定的業務需求來。在這里就只假設物流系統是共用的。
8.劃分限界上下文
限界上下文可以分為限界和上下文兩個詞來理解,限界指?個界限,具體的某?個范圍;上下文指的是場景、環境,所以限界上下文是在某個場景或環境下的業務邊界。
隨著業務的擴展漸漸的會產生越來越多的領域模型,任何一個比較大型的項目都會存在著很多的領域模型。如果把很多個領域模型相對應的軟件代碼全部放在一起,避免不了存在著很多有歧義的對象,使得代碼難于理解,找不到真正該改動的代碼導致 bug,所以需要通過限界上下文來明確定義領域模型的范圍和職責,來消除歧義。
要知道如何去劃分限界上下文,就需要知道劃分的規則:
術語相同,含義不同
在一個系統中,可能一個術語有著多重含義,這個時候就需要將領域模型分拆在多個界限上下文中,以避免歧義。一個上下文中的術語應該只有一種含義。
例如:術語“賬號”在不同的限界上下文中,含義是不同的。在上下文A中,賬號跟是和外部系統A對應的賬號;在上下文B中,它是這個系統本身的賬號,與外部系統A只有部分信息是一致的。
概念相同,用法不同
另一種情況就是雖然底層的概念是相同的,但是使用的?式不同,最終導致了不同的模型。
例如例子中的“玩具”,在整個系統中,物理上都對應著同一個玩具,共享了SKU數據。但置換上下文不能修改玩具的詳情,但其實在置換上下?中也不該關心玩具的具體詳細信息(玩具的圖片、破損情況、原本購買價格等等)。所以“玩具”雖然物理上是一個玩具,但是存在于不同的上下文中。
外部系統
一個系統常常避免不了與外界系統對接,為了避免內部系統受外部系統的影響所以需要分離上下文。
例如:玩具的具體物流情況是與一個物流系統對接的。如果以前是用的物流系統A,但是后面對接了物流系統B,那么就出現了不同的外部物流模型。這個時候內部的物流模型會受這些外部物流模型的影響,為了避免這種情況就要分離上下文,保持內部物流模型不受影響。
以上就是Event storming得到的一些結果,會發現最后得出的內容是向DDD靠攏的。那么在通過Event storming進行領域建模之后,如何根據構造好的模型來拆微服務呢?
微服務拆分原則
在這里對于微服務的內容不做過多的描述,只是單純的闡述如何根據領域模型來拆分微服務。
對于微服務的拆分原則,業界已有的共識:
1.【?選】依據業務限界上下?邊界拆分。這種方式拆分的微服務在顆粒度上?較適合剛開始做服務化架構的團隊,而且自然的在業務層面實現了高內聚[2]和低耦合[3]。
2.對于技術能??較成熟的團隊,可以依據聚合關系拆分服務。這種方式就將事物實時強?致性的要求,控制在了聚合范圍內,服務間則通過最終一致性保證事物的一致。通過聚合來拆服務使得服務的顆粒度更更?,拆出的服務也更多。但是這種方式對于開發和運維團隊的能力要求更?。
3.實際中也有一些別的服務拆分原則。例如按照變更的頻率拆分、按照?非功能需求拆分、按照組織結構拆分等等,這里就不詳細描述了,這些都是根據實際情況具體分析比較好。
[2] 高內聚:就是把相關的?為聚集在?起,把不相關的行為放在別處。如果你要修改某個服務
的行為,最好只在?處修改。
[3] 低耦合:如果做到了服務之間的松耦合,那么修改?個服務就不需要修改另?個服務。?個松耦合的服務應該盡可能少地知道與之協作的那些服務的信息。
總結
可以看出我們通過Event Storming的方式來構建DDD模型。通過構建好的模型,我們可以根據模型中的上下文或者聚合來拆微服務,這是實際場景中的應用。當然不是說這個模型只有拆微服務一個好處,它也能夠體現在軟件分層架構上,讓實際的代碼和模型相匹配,真正落地到項目實踐中,只是文中沒有這個部分的描述而已。