DDD領域驅動設計落地實踐系列:戰略設計和戰術設計

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"引言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過前面的文章介紹,相信大家對於什麼是DDD有了初步的瞭解,知道它是一種微服務的架構設計方法論,爲我們解決如何建立領域模型,如何實現微服務劃分等提供了方向和指導。但是對於如何具體落地使用DDD,可能大家還是一臉懵B的狀態,因此從本文開始以及後面的文章將對如何進行DDD落地進行詳細的闡述。在這其中還是會涉及到DDD中的一些重要概念,原本想着在一篇文章中介紹所有的概念,但是我覺得,概念總是在它該出現的時候出現纔會讓大家印象深刻,否則這些概念只是死板的概念,我們不清楚他爲什麼出現以及可以解決什麼問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"DDD大致實現過程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下圖所示,實現DDD落地大致需要經歷這樣三個階段,即爲業務分析-》戰略設計-》戰術設計,不同階段的輸出都是下一階段的輸入。業務分析階段爲戰略設計輸出經過統一語言描述的業務事件、業務邏輯以及業務分類,而戰略設計階段又爲戰術設計階段輸入領域模型以及邊界上下文,方便其進行微服務拆分以及模型映射。下面我們分別看下這三個階段到底都做了什麼事情。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"業務分析:","attrs":{}},{"type":"text","text":"在這個階段需要集齊項目團隊的成員主要包括領域專家、設計人員、開發人員等一起對業務問題域以及業務期望進行全面的梳理,釐請業務中的統一語言,在業務領域中發現領域事件、領域對象及其對應的領域行爲,搞清楚他們各自的關聯關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"戰略設計:","attrs":{}},{"type":"text","text":"通過DDD的理論,對業務進行領域劃分構建領域模型,梳理出相應的限界上下文,通過統一的領域語言從戰略層面進行領域劃分以及構建領域模型。在構建領域模型的過程中需要梳理出對應的聚合、實體、以及值對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"戰術設計:","attrs":{}},{"type":"text","text":"以領域模型爲戰術設計的輸入,以限界上下文作爲微服務劃分的邊界進行微服務拆分,在每個微服務中進行領域分層,實現領域模型對於代碼的映射,從而實現DDD的真正落地實施。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/16ca493c2260c9bf0a93a7948c104f46.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"相關概念補充","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在介紹戰略設計和戰術設計之前,我們先來弄清楚一些晦澀難懂的概念性的內容,這些概念看上去總是不明覺厲。我覺得DDD不容易入門的一個原因就是這些概念太不好理解了,即便是《領域驅動設計:軟件核心複雜性應對之道》原著中的描述更加讓人難以捉摸。要想理解這些概念性的內容,我們要思考爲什麼有這樣的概念,以及它的出現是爲了解決什麼問題,在實際的上下文場景下是怎麼運用的,我想只有這樣我們對這些不明覺厲的東東纔能有更加深刻的理解。我儘量結合一些實例來進行說明,方便大家的理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1、值對象","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"值對象看上去又是比較抽象的概念,越抽象的概念我們越要抓住最主要的脈絡,才能理解。就值對象來說,我們可以把他理解沒有ID的東東,什麼叫沒有ID呢?可以這麼理解,當我們在外面喫飯的時候,在餐廳裏面選位子落座,我們會關心這個桌子是哪裏生成的,編碼是什麼以及規格是怎樣的嗎?顯然不會,有空位坐就可以了。當然我們還是需要關注值對象上下文的,如剛纔的例子,我們消費者並不關心坐哪個位子,但是商家是關心的,因爲他要根據桌號來進行上菜以及收賬。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再來舉一個例子,在我們設計用戶信息的時候,這其中就會包括用戶的地址,用戶的地址實際是由這個用戶是哪個省的,哪個市的,哪個縣的以及具體地址是什麼、郵編是什麼等等屬性信息組成,那麼這個地址信息在用戶信息中實際就是一個值對象,他不需要唯一的ID來標識。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2、實體","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和值對象不同的是,實體是有唯一標識的,這就好比我們每個人都會有身份證,身份證上面的身份證號碼就是每個人的唯一標識。另外這個身份證號碼會一直跟着我們不會改變,即使你改了名字戶口狀態變化,但是身份證號碼是不會變化的,這也是實體的另一個特徵就是它具有延續性。實體對應了業務對象的業務屬性以及業務行爲。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"值對象以及實體都是領域模型中的領域對象,是組成領域模型的基礎元素,一起實現領域內的最基本的核心領域邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3、聚合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"又是一個看上去不好理解的概念,實際上用大白話來說,如果人是一個個不同的業務實體,那麼社會中的不同組織、機構就是將對應技能的人聚集在一起發揮更大的業務價值以及完成更加複雜的業務行爲的的集合。這就好比我們公司裏面有種各樣的部門,有人力部門負責招聘和薪酬、有銷售部門負責營銷、有研發部門負責產品研發。不同的部門實際就是不同的聚合。 聚合就是有業務關聯關係的實體以及值對象的集合,通過實體、值對象以及各自之間的業務邏輯聚合在一起完成某個業務節點,我們就可以理解爲聚合。我們可以根據業務的單一職責以及高內聚的的設計原來來進行聚合的劃分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4、聚合根","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"聚合的出現實際是一種業務單元,那它必定涉及到數據的持久化,如果在聚合中的任意實體都可以被外部進行數據修改,那麼我們將很難保證聚合內的數據一致性。因此我們需要有一個數據輸入修改的統一入口來保證聚合內的數據修改統一的符合聚合中的業務規則,因此出現了聚合根的概念。聚合根實際是就是一種實體,具備唯一標識,有獨立的生命週期。但是他是特殊的實體,他有協調實體以及值對象完成業務邏輯的功能,好比是一個部門的負責人一樣。一個聚合只有一個聚合根,聚合根在聚合之內採用引用依賴的方式對實體和值對象進行組織和協調,聚合根和聚合根之間通過唯一id進行聚合之間的協同。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"戰略設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"戰略設計這四個字聽起來有點高大上的感覺,感覺離我等DS比較遠。實際上無論對於公司或者個人來說,都需要有個發展的目標以及指導方針,指引我們該向何方前進,這個指導性的內容,我們就可以稱之爲戰略。那麼在DDD領域,戰略設計主要從業務角度出發,劃分業務的領域邊界,建立基於通用語言和業務上下文語義邊界的限界上下文,實現業務領域模型的構建。使得限界上下文可以作爲微服務拆分和設計的邊界。因爲必須先有邊界清晰的領域模型以及上下文,我們才能設計出邊界清晰的微服務。因此在戰略設計階段最重要的就是限界上下文以及領域建模。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常在業務分析階段我們把業務域的主體流程進行了梳理以及分析,同事將業務期望進行了定義。在進行戰略設計的過程中,需要對業務領域進行全面的梳理,首先對業務進行領域切分,劃分出業務的上下文邊界,然後建立對應的限界上文中的領域模型,上下文邊界和領域模型可以做誒微服務拆分拆分設計的輸入,指導微服務落地時的拆分設計。其中業務中,對應的領域模型十分重要。那麼我們該採用什麼方法才能從複雜的業務領域中構建出高內聚低耦合的領域模型呢?我們可以通過業務分析以及領域建模兩個階段來實現領域模型的構建。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"業務分析","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1、事件風暴","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過事件風暴的方法可以實現快速分析和分解複雜的業務領域,分析並提取出對應的領域模型。事件風暴是早2013年提出來的,通過組織領域專家、以及項目團隊成員在一起通過頭腦風暴的方式,通過梳理業務領域中所有的領域事件以及領域事件的參與者以及輸入。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"參與者","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要包括領域專家、DDD專家、架構師、產品經理、項目經理、開發人員以及測試人員等","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實際活動","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當把參與者集合在一起之後,大家需要通過頭腦風暴的方式梳理當前的業務域問題。比如某個業務動作或者行爲是否會觸發下一個業務動作,這個動作(領域事件)的輸入和輸出是什麼,是誰(實體)發出的什麼動作(命令),觸發了這個動作,這些我們都需要梳理清楚。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0a/0a07e0653c0969be72a6ede6376937a7.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們以大家最熟悉的電商系統來舉個栗子,在電商業務中有一個重要的環節就是物流。因此物流是電商業務的重要業務子域。當用戶下完訂單之後,在倉儲領域就需要陳誠對應的貨物揀貨單,系統根據揀貨單中的貨物給不同分區的倉內作業人員生成對應的揀貨任務,作業人員根據對應的揀貨任務進行貨品的揀取,並放入相應的貨物容器中。最終將所有訂單商品在揀貨完成後進行合流,進行校驗,最終形成包裹再進行後續的配送流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在這個流程當中,我們就會涉及到的實體就有訂單、揀貨單、揀貨任務、用戶、容器等,對應的值對象爲地址信息。領域事件爲揀貨單生成、揀貨任務生成。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"領域建模","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我看來,領域建模實際上是整個DDD領域驅動設計中最重要的環節。對上,好的領域模型說明對業務流程的梳理以及業務領域的抽象做得好。對下,高內聚低耦合的領域模型可以直接決定微服務設計的質量和水平。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1、找出實體和值對象","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過全盤的業務梳理之後,我們可以在業務對象中找到對應的實體以及值對象,這裏面就會牽扯到哪些領域對象設計未實體,哪些領域對象設計爲值對象。主要還是根據實際的業務特徵來決定。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2、構建聚合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過業務梳理,我們找到了業務下所有的實體以及值對象,接下來我們就要構建聚合了。在構建聚合之前,我們需要先從實體集合中找到聚合根,這就好比打仗的時候講究擒賊先擒王,王擒到了之後,歸屬於下面的小兵就會乖乖就範了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼我們應該怎麼判斷一個實體它就是聚合根呢?主要是看這個實體是不是有專門的模塊來進行維護,自身是不是有完整的獨立的生命週期以及是不是有全局的唯一ID。通過這幾個判斷條件我們很容易找到對應的聚合根,如下圖所示,在倉內進行作業的任務,其中揀貨單就是一個聚合根,滿足上述的幾個條件,同時可以將和其有業務關聯的實體例如貨物、揀貨容器等歸併到揀貨單,最終形成揀貨聚合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c6/c65b262f158843e0b39ad9e05e9f9b08.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3、劃分聚合到邊界上下文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們需要將之前梳理出來的領域事件,事件流轉的觸發命令都全部羅列出來,在這個過程中提取出產生業務行爲的對象,就是前文所說的實體。如前面所說的物流域,庫存、容器。在這個過程中我們需要找出對應的實體以及值對象,同時在這些實體中找出聚合根,將存在存在緊密業務邏輯關係的聚合根、實體以及值對象劃分到一起形成聚合,再根據之前劃分的邊界上下文將多個聚合滑到限界上下文中。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"戰術設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同於戰略設計的高屋建瓴,戰術設計是從實際的技術角度出發,它更加側重於領域模型的技術實現,按照領域模型完成微服務的開發以及落地。從某種意義來說,戰略設計實際就是戰術設計的輸入。在戰術設計中會有聚合、聚合根、實體、值對象、領域服務、領域事件的代碼實現,通過將這些領域對象映射到代碼中實現設計以及系統的落地。在這個階段,我們需要梳理微服務的邊界以及邊界內的領域對象以及它們之間的關係,並且可以實際的將這些領域模型確定在哪些代碼模塊中以及微服務分層中的具體位置。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過DDD按照一定的規則對業務領域進行細分,是的問題範圍可以限定在指定的邊界中,因此我們可以在這個邊界內建立領域模型,而後再通過代碼實現領域模型,從而解決相應的業務問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此在戰術設計階段,我們有個重要的事項需要去完成,一個是微服務的領域對象分析與邊界劃分,另一個是微服務的結構分層。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"微服務領域對象分析與邊界劃分","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在戰略設計階段,我們已經構建的業務領域模型,領域模型中包含了實體、值對象、聚合根。在上文中我們實際已經根據一些業務域進行了聚合的劃分,那麼在此處我們需要根據不同的聚合以及已有的限界上下文繼續劃分微服務。有的微服務會包含多個聚合,有的微服務只有一個聚合。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/5463993b4ba035d378e38c44fd50397c.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"微服務結構分層","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在傳統的工程代碼結構中,大都採用MVC的工程結構。但是隨着微服務時代的來臨,傳統的工程結構不能滿足快速變化的業務需求。另外隨着DDD領域驅動設計的落地,需要對於微服務的工程結構有更進一步的進化和升級。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/04/046ce09e488340e2b6536c6183a70969.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Eric Evans在《領域驅動設計:軟件核心複雜性應對之道》文中提出了傳統的四層結構,但是實際上存在一定的問題。在DDD領域驅動設計中,領域層應該是核心層,但是傳統的四層結構中基礎層卻處在覈心位置,有點本末倒置的感覺。所以應該採用以領域層爲核心的新的四層結構的方式優化原有的結構,實現對於基礎層的解耦。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"基礎層","attrs":{}},{"type":"text","text":":實際就是未微服務的其它層提供基礎的通用技術支撐,如Redis、MQ以及數據庫等,常見的就是數據庫持久化可以放在這層當中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用戶接口層:","attrs":{}},{"type":"text","text":"負責向用戶展示信息以及轉化用戶請求意圖,在前後端分離的當下,可以實現在後端服務不變的情況下適配不同的用戶前端的作用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"應用層:","attrs":{}},{"type":"text","text":"可以理解爲實現領域對象以及多個聚合的服務編排以及組合,不應該有過多的業務邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"領域層:","attrs":{}},{"type":"text","text":"領域層實際就是整個微服務的核心,在這其中涉及到領域對象的㛑狀態變化以及領域規則。領域層主要包括實體、值對象、聚合根以及領域服務等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然實際上給還有其他的分層結構比如五層分層結構、六邊形分層結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文主要圍繞DDD領域驅動設計落地時間的三大過程進行了闡述,詳細說明了戰略設計階段以及戰術設計階段的輸入和輸出。希望對於大家怎麼去實施DDD領域驅動設計有一個方向的指引,後面的文章將繼續深入闡述DDD具體落地實踐的細節和一些建議。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章