在战略设计层面提出了域、子域、限界上下文等重要概念;在战术设计层面提出了实体、值对象、领域服务、领域事件、聚合、工厂、资源库等重要概念。 领域驱动设计 有防腐层的设计
小結:
1、
“分層架構”遵循了“關注點分離”原則,將屬于業務邏輯的關注點放到領域層(Domain Layer)中,而將支撐業務邏輯的技術實現放到基礎設施層(Infrastructure Layer)中。同時,領域驅動設計又頗具創見的引入了應用層(Application Layer)。應用層扮演了雙重角色。一方面它作為業務邏輯的外觀(Facade),暴露了能夠體現業務用例的應用服務接口;另一方面它又是業務邏輯與技術實現的粘合劑,實現二者之間的協作。
2、
DDD的核心是從業務出發、面向業務變化構建軟件架構,實質是保證面對業務變化時我們能夠有足夠快的響應能力。面向業務變化而架構就要求首先理解業務的核心問題,即有針對性地進行關注點分離來找到相對內聚的業務活動形成子問題域。
3、
Martin Fowler和James Lewis提出微服務時,提出了微服務的9大架構特質,指導組織圍繞業務組建團隊,把業務拆分為一個個業務上高度內聚、技術上松散耦合、運行在獨立進程中的小型服務,微服務架構賦予了每個服務業務上的敏捷性和技術上的自主性,因此可以針對每個服務進行獨立地迭代、更新、部署和彈性擴展,從而縮短需求交付周期并加速創新。
https://mp.weixin.qq.com/s/jo3jNikkxrM4ouzvjA-fFQ
領域驅動設計詳解:是什么、為什么、怎么做?
原創阿里巴巴文娛技術阿里技術2020-07-27
導讀:什么是領域驅動設計?傳統分層架構在實際開發中存在哪些問題?業務開發人員如何設計并搭建自己的領域模型?阿里文娛技術專家戰獒將為大家一一解答,并分享文娛在領域驅動設計上的實踐。
文末福利:下載《為業務量身打造——阿里文娛用戶及內容運營平臺技術實踐》電子書。
一 什么是領域驅動設計
領域驅動設計的概念是2004年Evic Evans在他的著作《Domain-Driven Design : Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計:軟件核心復雜性應對之道)中提出的,從領域驅動設計提出距今已經有15年的時間,為什么最近才開始在中國的互聯網圈大行其道?似乎一夜之間大家都在談論,那么領域驅動設計到底幫我們解決了什么問題?帶著這些疑問,一起來看下阿里巴巴文娛是如何實踐領域驅動設計的。
二 領域驅動設計大行其道的必然原因
軟件系統從來都不是憑空而來,而是以軟件的形式解決特定的問題。當我們面臨現實世界的復雜問題時,如何以軟件的形式落地?領域驅動設計是一套方法論,指導我們將復雜問題進行拆分、拆分出各個子系統間的關聯以及是如何運轉的,幫助我們解決大型的復雜系統在落地中遇到的問題。
Evic Evans在著作中將軟件系統的設計分為2個部分:戰略設計和戰術設計。在戰略設計層面提出了域、子域、限界上下文等重要概念;在戰術設計層面提出了實體、值對象、領域服務、領域事件、聚合、工廠、資源庫等重要概念。如圖1所示:
圖1 戰略設計與戰術設計
戰略設計部分指導我們如何拆分一個復雜的系統,戰術部分指導我們對于拆分出來的單個子系統如何進行落地,在落地過程中應該遵循哪些原則。
以大家熟知的電子商務系統舉例,早期的電商系統因為業務相對簡單,用戶量和團隊規模也較小,一個單體應用就可以搞定,隨著容量上升可以將單體應用進行橫向擴容,比如早期的淘寶就是這樣做的。拆分過程中我們可以把電商系統這個單體應用拆分成訂單子系統、庫存子系統、物流子系統、搜索推薦子系統等等,如圖2所示:
圖2電商系統微服務劃分
領域驅動設計在戰略層面上的域、子域、限界上下文的劃分思想和微服務的劃分不謀而合。域對應一個問題空間,也就是上例中的電商系統;子域是把域這個大的問題空間拆分成若干個小的更容易解決的問題空間,也就是單體應用向微服務演進過程中劃分出來的各個子系統;限界上下文是解決方案空間,每個子域對應一個或多個解決方案空間。微服務的劃分是也是將一個大的問題拆分成若干個小的問題,每一個小的問題用一個或多個微服務來解決。
對于大多數開發同學來說都沒有機會接觸系統的劃分,這些工作一般是公司的技術領導層與架構師來做的,普通的開發同學日常工作中接觸到的只是某一個具體微服務或微服務中某一個模塊的落地,那是不是說領域驅動設計對于普通開發同學來說就沒有用了?當然不是這樣,領域驅動設計中的戰術設計部分就是指導我們如何落地一個系統才可以使系統具備高可擴展性、高可讀性。
所有的系統最終都要以代碼的形式落地,而落地的工作都是由普通的開發同學來做的,系統是否具備高可擴展性、高可讀性直接影響了整個團隊的效率。
三 傳統分層架構存在的問題
對于大多數開發同學來說,大部分時間都花在落地一個個微服務上,下面我們來看阿里文娛是如何結合領域驅動設計的思想將微服務進行戰術落地的。
目前筆者接觸過的微服務大多數都是分層架構并且在Service層與Manager層實現具體的業務邏輯,使用DO、DTO、BO、VO等進行數據傳輸,數據和行為基本完全隔離。這種分層結構圖3是《Java開發手冊》中的標準分層結構。
該規范中定義了各層的職責,其中最重要的兩層Service層和Manager層是這樣規范的(以下兩層解釋摘抄自《Java 開發手冊》):
Service層
相對具體的業務邏輯服務層。
Manager層
通用業務處理層,它有如下特征:
對第三方平臺封裝的層,預處理返回結果及轉化異常信息。
對Service層通用能力的下沉,如緩存方案、中間件通用處理。
與DAO層交互,對多個DAO的組合復用。
圖3 傳統的分層結構
阿里文娛早期的項目分層也基本都采用這種架構形式。上面的分層并沒有問題,但是這種分層架構采用的是包的形式進行的層與層的隔離,需要每一位開發同學理解并且自覺遵守以上規范,但是在實際工作中我們發現很多同學對Service層和Manager層的區別并不是特別的清楚,即使清楚的同學大部分也并沒有完全遵守手冊中的規范,這種現象導致Manager層除了沉底一些通用能力以外和Service層并沒有什么本質區別。
在實際的業務代碼中Service層和Manager層都充斥了大量的第三方依賴,對系統的穩定性有很大的影響。每依賴一個第三方服務都要各種異常問題,這些異常處理的代碼往往會和業務代碼混在一起,當這種代碼多了以后會使代碼的可讀性非常差。
阿里文娛業務的復雜度提升很快,業務迭代速度也很快, Service層和Manager層代碼量迅速膨脹,業務邏輯變得越來越復雜。在這種業務場景下,大文娛引入了領域驅動設計并設計了一套完整的領域驅動模型評估與演進的解決方案來輔助開發同學將領域驅動設計的思想真正的落地。
四 文娛領域驅動設計實踐
領域驅動設計的關鍵在于識別業務的模型,而模型又是會隨著業務的發展而演進的,對于新的業務來說能效平臺提供了業務模型分析的功能,開發同學可以在能效平臺設計并搭建自己的領域模型,搭建出來后能效平臺可以評估領域模型設計的是否合理,如果模型設計合理則可以基于以上設計的模型符合領域模型規范的代碼。對于已有應用,能效平臺設計了一套領域注解并以SDK的形式提供出去:
第一步:開發同學按照領域設計的原則對業務代碼進行分析并打上注解。
第二步:能效平臺可自動掃描該項目并收集該項目中的領域模型。
第三步:模型收集后,開發同學可以在能效平臺改進業務模型并重新按照領域模型的規范生成代碼。
完整流程如下圖所示:
1 模型采集
對于已有的準備重構的應用,我們設計了一套領域模型的注解,開發同學可以將注解加到對應的類、屬性、方法上。當系統是按數據模型落地而不是按領域模型的方式落地時,可以先找到系統的數據模型,然后在能效平臺對數據模型進行組織生成領域模型。
圖5 領域模型注解
2 模型搭建
對于新應用或者已經進行完模型采集的應用,開發同學可以在能效平臺進行模型的搭建和修改,如圖6所示。
圖6 領域模型
3 健康度評估
對于已經搭建完的模型能效平臺,根據領域驅動設計的規范創建了一套完整的校驗規則,模型搭建完成在生成腳手架之前會根據校驗規則進行打分,當打分通過時可以將模型生成腳手架。
圖7 模型校驗
4 腳手架生成
當模型搭建完畢并且校驗通過后可以將模型生成腳手架,其代碼結構是按照六邊形架構的標準生成的,六邊形架構也成為端口與適配器架構,該架構的思想是將內部核心的領域邏輯與外界依賴進行隔離,這里的依賴是指所有對其他微服務的依賴、http的依賴、數據庫依賴、緩存依賴、消息中間件依賴等等,所有的這些依賴都通過適配器進行轉換成應用可理解可識別的最小化信息。
在實際的項目中,每種依賴都要考慮各種異常情況并進行處理,而這些處理實際上并不數據領域邏輯,卻耦合到了業務代碼里,當這種依賴多了對系統的穩定性會產生很大的影響,傳統的分層架構雖然也會讓我們將自身的領域邏輯和依賴進行分離,在阿里巴巴規范手冊中提到所有的依賴都應該放到Manager層,但是這種規范是非常容易被打破的。六邊形架構從應用分層上讓我們更容易去遵守這樣的規范。
根據六邊形架構的指導思想,在實際的應用分層中一般劃分為四層,分別是:
用戶接口層:負責用戶展現相關的邏輯。
應用層:負責對一個用例進行流程編排(將接口用例分成若干個步驟,但是不負責每步的具體實施)。
領域層:負責實現核心的領域邏輯即業務邏輯(負責實現具體的業務邏輯)。
基礎設施層:所有依賴的具體實現。
但是從應用架構的角度看,層級組織形式可以分為兩種:
傳統分層架構
如圖9左側,這種分層架構是Evic Evans在《Domain-Driven Design : Tackling Complexity in the Heart of Software》中提出的,其中用戶接口層、應用層、領域層可直接依賴基礎設施層,與圖3的傳統架構并無本質區別,因為所有層都直接依賴了基礎設施層。這種方式需要強制開發同學將所有的依賴進行下沉,隨著時間的推移這種規范非常容易被打破。
9層依賴關系
依賴倒置的分層架構
如圖9右側,這種分層架構是依賴倒置的分層架構,特點是:
基礎設計層可直接依賴其他三層,反之則不行。
用戶接口層、應用層、領域層如果要使用基礎設施層中的能力,只能通過IOC的方式進行依賴注入,這也遵從了面向對象編程中的依賴倒置原則。
當開發同學要在以上三層中直接引用第三方依賴時,是找不到具體的類信息的,也就是不能import。同時這種方式對單元測試的規范也可以起到很大的作用,當我們編寫單元測試時可以為領域層注入一個測試運行時的依賴,這樣應用運行單元測試可以不依賴下游服務,在代碼層面上也更加規范。
五 總結
經典的三層或多層架構雖然是目前最普遍的架構,但是在隔離方面做得并不夠好。在業務架構選型時要結合自身業務特點,而不能千篇一律的選擇某一種業務架構,合適的業務架構可以延長項目的生命周期,降低項目的重構頻率,最終達到降低人力成本的目的。
https://mp.weixin.qq.com/s/E21dEg_6lUObZz36DER6zA
平臺研發朱志國京東零售技術2020-02-24
過去幾年通天塔一直處于快速的業務能力建設和架構完善的階段,以應對不斷增長的業務需求和容量、高可用等技術需求,現在通天塔平臺已經能滿足集團主站的大部分活動、頻道搭建和運營能力,主流程的新需求越來越少,個性化需求和非標準化流程的數據源和服務接入的需求越來越多,有些甚至是京東零售體系外的,同時通天塔技術和產品也在積極主動尋求變化和創新,這些因素結合在一起驅動通天塔孵化出了一個以技術為導向的項目:通天塔積木,旨在構建一個基于完全開放的前端SDK和后端數據源&服務、高度靈活和強大的積木畫布、能夠快速移植和部署到任何第三方IT環境的活動搭建解決方案,這套方案的初衷和設計理念也契合了京東國際化賦能和PaaS化的戰略。目前通天塔積木已經取得階段性成果,已開始賦能京東國內和國際站,但如何應對異常復雜的積木業務邏輯和不可預知的業務變化,構建業務和底層技術基礎實施的完全解耦的系統,一直是我們面對的巨大挑戰。也是時候從更高視角來看清問題和源頭,思考一種能應對和控制業務復雜度、具備強擴展性和彈性的解決方案。縱觀我們的目標,DDD這個詞不知不覺映入了我的眼簾。
2004年著名建模專家Eric Evans發表了他最具影響力的書籍《Domain-Driven Design –Tackling Complexity in the Heart of Software》(領域驅動設計—軟件核心復雜性應對之道),書中反復強調領域通用語言(Ubiquitous Language)的重要性,全面闡述了DDD戰略設計到戰術設計的方法論和實踐。讓軟件研發所有參與者圍繞著一個統一和一致的領域模型建模和設計,分析模型和設計模型不再割裂,并引出了以領域為核心的分層架構,有效地分離業務和技術復雜度,使得領域層的代碼和領域模型保持高度一致。在戰術上提供了諸多元模式幫助構建職責清晰、內聚和高維護性和可擴展性的代碼。
領域驅動設計不是新鮮的概念,至今已有十六年時間,一直來不曾大行其道,直到IT行業內掀起微服務的狂潮,技術界才重新審視和意識到領域驅動設計的價值。不能說微服務拯救了領域驅動設計,但確實是微服務,讓領域驅動設計又重新煥發了青春。DDD是一個非常龐大的建模和設計體系,這篇文章只在理論和概念上闡述DDD的價值、方法和架構,歡迎任何的問題指正和補充。
DDD價值
1.1應對復雜業務
引起軟件系統復雜度的主要因素是需求,軟件系統需求又可以分兩個方面:業務需求和技術需求。我們分析系統的復雜度時就可以從業務復雜度和技術復雜度這兩個維度出發。
業務復雜度跟系統的業務需求規模和需求之間的關系層級有直接關系,需求的數量和關系的層級決定代碼的規模和邏輯循環或遞歸的層級,系統的需求數量越大,需求之間的關系越復雜,系統的業務復雜度就越大。John Ousterhout的著作《A Philosophy of Software Design》從認知的負擔和開發工作量的角度來定義軟件系統的復雜度,并給出了一個復雜度公式:
子模塊的復雜度(cp)乘以該模塊對應的開發時間權重值(tp),累加后得到系統的整體復雜度(C)。可以看到系統整體的復雜度并不簡單等于所有子模塊復雜度的累加,還要考慮該模塊的開發維護所花費的時間在整體時間中的權重占比(tp),這個權重比就跟模塊劃分是否內聚、設計是否優雅有直接關系。
技術復雜度則來自于對軟件系統運行的質量需求,包括安全、高性能、高并發、高可用和高擴展性。系統安全性要求對訪問進行控制,無論是加密還是認證和授權,都需要為整個系統架構添加額外的間接層。不僅對訪問的低延遲產生影響,還極大提升了系統代碼復雜度;為了讓后端系統能具備高擴展性和彈性,要求所有系統的設計必須是無狀態的;為了提升用戶端訪問體驗,后端需要增添離線任務對數據加工、異構、預熱、預緩存,以實現用空間換時間,降低實時接口的邏輯復雜度來降低請求的延遲。然而最讓開發者更抓狂的是這些技術需求彼此又是相互影響甚至相互矛盾,在一些復雜流程并要求高響應的業務場景,如下單、秒殺等,會將一個同步的訪問請求拆分為多級步驟的異步請求,再通過引入消息中間件對這些請求進行整合和分散處理,這種分離一方面增加了系統架構的復雜性,另一方面也因為引入了更多的資源,使得系統的高可用面臨挑戰,并增加了維護數據一致性的難度。而且技術復雜度與業務復雜度并非孤立,二者復雜度因子混合在一起產生的負作用更讓系統的復雜度變得不可預期,難以掌控,就好比氫氣和氯氣混合在一起遇到光亮發生爆炸一樣。
DDD的核心思想就是要避免業務邏輯的復雜度與技術實現的復雜度混淆在一起,確定業務邏輯與技術實現的邊界,從而隔離各自的復雜度,業務邏輯并不關心技術是如何實現的。無論采用何種技術,只要業務需求不變,業務規則就不會變化。理想狀態下,應該保證業務邏輯與技術實現是正交的。DDD通過分層架構與六邊形架構確保業務邏輯與技術實現的隔離。
DDD戰略設計指導我們面對客戶的業務需求,由領域專家與開發團隊展開充分的交流,經過需求分析與知識提煉,獲得清晰的問題域,在引入限界上下文和上下文映射對問題域進行合理的分解,識別出核心領域與子領域,并確定領域的邊界以及它們之間的關系,從而把一個大的復雜系統問題拆分成多個細粒度、獨立和內聚的業務子問題,從而很好地分解和控制業務復雜度,各個小組聚焦各自的子領域中。在架構方面,通過分層架構來隔離關注點,將領域實現獨立出來,利于領域模型的單一性與穩定性;引入六邊形架構清晰地界定領域與技術基礎設施的邊界;CQRS模式則分離了查詢場景和命令場景,針對不同場景選擇使用同步或異步操作,提高架構的低延遲性與高并發能力。
1.1.1、分層架構
“分層架構”遵循了“關注點分離”原則,將屬于業務邏輯的關注點放到領域層(Domain Layer)中,而將支撐業務邏輯的技術實現放到基礎設施層(Infrastructure Layer)中。同時,領域驅動設計又頗具創見的引入了應用層(Application Layer)。應用層扮演了雙重角色。一方面它作為業務邏輯的外觀(Facade),暴露了能夠體現業務用例的應用服務接口;另一方面它又是業務邏輯與技術實現的粘合劑,實現二者之間的協作。下圖展現的就是一個典型的領域驅動設計分層架構。藍色區域的內容與業務邏輯有關,灰色區域的內容與技術實現有關,二者涇渭分明,然后匯合在應用層。應用層確定了業務邏輯與技術實現的邊界,通過直接依賴或者依賴注入(DI,Dependency Injection)的方式將二者結合起來。
1.1.2、六邊形架構
由Cockburn提出的六邊形架構則以“內外分離”的方式,更加清晰地勾勒出業務邏輯與技術實現的邊界,且將業務邏輯放在了架構的核心位置。這種架構模式改變了我們觀察系統架構的視角。
體現業務邏輯的應用層與領域層處于六邊形架構的內核,并通過內部的六邊形邊界與基礎設施的模塊隔離開。當我們在進行軟件開發時,只要恪守架構上的六邊形邊界,就不會讓技術實現的復雜度污染到業務邏輯,保證了領域的整潔。邊界還隔離了變化產生的影響。如果我們在領域層或應用層抽象了技術實現的接口,再通過依賴注入將控制的方向倒轉,業務內核就會變得更加的穩定,不會因為技術選型或其他決策的變化而導致領域代碼的修改。
1.2快速響應業務變化
不確定性和變化是這個時代的主旋律,業務需要快速上線,并根據用戶的反饋不停地調整和升級,有生命力的業務主動尋求變化,不變則亡是很多行業目前的共識,企業應對變化的響應力成了成敗的關鍵。同時一個長期困擾軟件研發的問題是,需求總是在變化,無論預先設計如何“精確”,總是發現下一個坑就在不遠處。相信很多技術人員都有這樣的經歷,架構和響應能力越來越糟糕,也就是我們常說的架構腐化了,最后大家不得不接受重寫。軟件架構設計的另一個關鍵方面是讓系統能夠更快地響應外界業務的變化,并且使得系統能夠持續演進。在遇到變化時不需要從頭開始,保證實現成本得到有效控制。
DDD的核心是從業務出發、面向業務變化構建軟件架構,實質是保證面對業務變化時我們能夠有足夠快的響應能力。面向業務變化而架構就要求首先理解業務的核心問題,即有針對性地進行關注點分離來找到相對內聚的業務活動形成子問題域。讓每個字問題的劃分盡可能靠近變化的原點,子問題域內部是相對穩定的,未來的變化頻率不會很高,是符合深模塊特性的,而子問題邊界是很容易變化的。DDD最后在實現層面利用成熟的技術模式屏蔽掉技術細節的復雜度。
1.3與微服務相得益彰
Martin Fowler和James Lewis提出微服務時,提出了微服務的9大架構特質,指導組織圍繞業務組建團隊,把業務拆分為一個個業務上高度內聚、技術上松散耦合、運行在獨立進程中的小型服務,微服務架構賦予了每個服務業務上的敏捷性和技術上的自主性,因此可以針對每個服務進行獨立地迭代、更新、部署和彈性擴展,從而縮短需求交付周期并加速創新。
在面對復雜業務和快速變化需求時,DDD從業務視角進行關注點分離和應對復雜度,讓業務具備更高的響應力。DDD戰略設計階段,引入限界上下文(Bounded Context)和上下文映射(Context Map)對問題域進行合理的分解,確定領域的邊界以及它們之間的關系,維持模型的完整性。限界上下文不僅限于對領域模型的控制,而在于分離關注點之后,使得整個上下文可以成為獨立部署的設計單元,這就是“微服務”的概念,上下文映射的諸多模式則對應了微服務之間的協作。因此在戰略設計階段,微服務擴展了領域驅動設計的內容,反過來領域驅動設計又能夠保證良好的微服務設計。邊界給了實現限界上下文內部的最大自由度。這也是戰略設計在分治上起到的效用,我們可以在不同的限界上下文選擇不同的架構模式和技術實現,這也正好映照了微服務的特點:在技術架構上,系統模塊之間充分解耦,可以自由地選擇合適的技術架構,去中心化地治理技術和數據。
Thought Works公司技術專家編寫的《微服務設計》書中,專門有一章節“限界上下文”,充分說明微服務的落地需要DDD來輔助的,起碼在建模階段是需要借助DDD強大的戰略模式來支撐的。微服務不是簡單的指將服務盡可能的拆小,然后一個RPC框架搞定了,這太粗糙了,無法落地。
1.4輔助中臺戰略落地
領域驅動設計讓參與者基于統一語言溝通和協作,圍繞一個統一和一致的領域模型工作,傳統的分析模型和設計模型不再割裂;顯式地把業務領域和設計放到了軟件開發的核心,軟件人員和業務人員合作來構建領域模型,使得軟件的交付質量更高且維護成本更低;利用限界上下文來分解問題域,識別核心領域,有效分解和控制了業務的復雜度;利用DDD提倡的分層、六邊形等架構,分離了業務復雜度和技術復雜度,使得系統具備更強的擴展性和彈性;戰術層面提供了元模型(聚合,實體,值對象,服務,工廠,倉儲)幫助構建清晰、穩定,能快速響應變化和新需求能力的應用;DDD構建的應用能快速方便地切到微服務;領域驅動設計給企業應用帶來的穩定性、靈活性、擴展性和應對變化的響應力對于建立靈活前臺、穩固中臺能帶來巨大的幫助作用。
DDD過程
領域驅動設計是一套面對復雜業務進行建模和設計的方法論和實踐,建立了以領域為核心驅動力的設計體系。領域驅動設計分為2個主要過程:戰略設計、戰術設計。
在戰略設計階段,面對紛繁復雜的業務需求,領域專家和研發團隊進行緊密合作、充分溝通,進行事件風暴或場景驅動設計,分析需求并提煉知識,得到比較清晰的問題域,輸出由領域專家和研發團隊達成共識的統一語言(UL,Ubiquitous Language),基于統一語言對問題域進行分析和建模,識別業務邊界,確定限界上下文,根據限界上下文劃分獨立的領域,建立限界上下文彼此之間的關系,接著引入系統上下文(System Context)確定系統的邊界,并確定它的外部環境,包括與其集成的第三方系統與基礎設施。利用DDD分層架構或六邊形架構界定業務領域和技術實現的邊界,讓穩定的核心領域模型處于架構的最內部,避免技術實現和架構變動帶來的影響。
接著進入戰術設計階段,一個大的業務問題被分解為多個限界上下文(問題域),團隊視野和專注就可以聚焦到每一個內聚的限界上下文,進行戰術設計。戰術設計的重點是利用領域驅動設計的元模型對領域的復雜性進行分解和建模。
領域驅動設計強調和突出了領域模型的重要性,通過整個領域驅動設計過程,綁定領域模型和技術模型,以保證領域模型和技術模型在貫穿整個軟件開發的生命周期中(需求分析、建模、架構、設計、編碼、測試與持續重構)的強一致性。領域模型指導著軟件設計以及技術編碼實現,接著通過重構實踐來挖掘隱式概念,完善統一語言和模型,運用設計模式改進設計與開發質量。以下是領域驅動設計的粗略過程:
2.1戰略設計
2.1.1、提煉問題域
回顧我們往日的分析和解決問題過程, 面對復雜問題,很多同學還沒完全理解問題的全貌就已經在提出解決辦法,這些解決辦法只是針對問題的局部,經典圖書《第五項修煉》把這種行為稱為“反應式”的,碰到一個問題給出一個回應辦法,而從這些問題整體來看這種方式會阻礙團隊找出最佳解決方案。DDD作為一種建模和架構方法,最大的突破是著重明確了區分了問題域和解決方案域,對業務問題的認知不是技術人員最擅長的,很多研發在碰到需求時,腦子本能就閃現表、類、服務、架構,把解決方案當終極問題來追求,而DDD要求研發進行痛苦的蛻變,在業務分析和領域建模階段忘記技術解決方案。同時DDD要求領域專家和技術人員坐在一起通力合作、密切溝通來分析和建模,領域專家對業務有著深刻的理解,技術人員擅長技術實現和架構設計,而領域專家和技術人員由于工種的差異導致交流產生障礙,開發人員滿腦子是技術語言,領域專家腦子也都是業務概念,如果按照本能基于自己的專業背景進行溝通,效率太低了,即使有翻譯的角色也會產生理解偏差,DDD的一個核心原則是所有人員包括領域專家和技術的進行任何溝通都使用一種基于模型的通用語言(UL,Ubiquitous Language),在代碼中也是這樣。
DDD幫助技術人員對需求進行本質思考和理解,關注點不在是聚焦在功能上,而是理解需求的真正意圖和愿景,而非開發一個feature,更深層次地理解隱含的愿景才能開發出真正地解決問題和創造價值的系統來。在提煉問題域過程中,領域專家和技術專家通過充分交流,進行需求分析和知識提煉,獲得清晰的問題子域,識別出核心域、通用域、支撐域。通用域是開發該軟件系統根本競爭力所在,也是領域建模的重心,建議分配最精銳的研發;通用域是指多個子域依賴的通用功能子域,比如權限、郵件、日志系統等;支撐域是指系統中非核心域和通用域的業務域。需求分析時從用例開始,列出達成業務目標需要的步驟,切忌跳轉到解決方案上,識別出用于構建模型的知識,通過UML表示分析模型和業務模型,形成業務和技術人員達成共識的通用語言。
該階段領域專家只專注于問題域而不是解決方案,業務和技術人員基于UL溝通,并且考慮投入產出比,團隊只為核心業務進行領域驅動設計并創建UL,訂單系統為下單模塊進行DDD,訂單監控模塊用普通的事務腳本方式來即可,我們通天塔的活動模板和積木業務非常復雜和核心,非常適合使用DDD來建模和架構設計,而通天塔后端的Man系統是面向開發者進行后端和線上業務監控的,進行DDD就是小題大做。
2.1.2、識別限界上下文(Bounded Context)
Eric Evans說:“對一個大型系統,領域模型的完全統一將是不可行的或者不劃算的。”。DDD的構建塊不能盲目地應用在一個無限大的領域模型上,一個無限大的領域模型也無助于我們開發出優質的軟件,限界上下文是分解領域模型的關鍵。限界上下文是一種“分而治之”的思維,也是一種高層的抽象機制,讓人們對領域進行本質思考,簡化問題和應對復雜性。
限界上下文如同細胞,細胞是上下文,細胞壁是邊界,細胞內的信息負責對代謝和遺傳進行調控,細胞壁對細胞起著支持和保護防御的作用,控制物質進出,讓對細胞有用的物質不能出來,有害的物質也不能進入細胞。而領域驅動設計中的限界上下文保證領域模型的一致性和完整性,清晰邊界的控制力保證了領域的安全和穩定。
2.1.2.1、如何識別限界上下文?
明確了系統的問題域和業務期望后,梳理出主要的業務流程,這些業務流程體現了各種參與者在這個過程中通過業務活動共同協作,最終完成具有業務價值的領域功能。業務流程結合了參與角色(Who)、業務活動(What)和業務價值(Why)。在業務流程的基礎上,我們就可以抽象出不同的業務場景,這些業務場景又由多個業務活動組成,可以利用領域場景分析方法剖析場景,以幫助我們識別業務活動,例如采用用例對場景進行分析,此時,一個業務活動實則就是一個用例。業務流程是一個由多個用戶角色參與的動態過程,而業務場景則是這些用戶角色執行業務活動的靜態上下文。
接下來,我們利用領域場景分析的用例分析方法剖析這些場景。通過參與者(Actor)來驅動對用例的識別,這些參與者恰好就是參與到場景業務活動的角色。根據用例描述出來的業務活動應該與統一語言一致,最好直接從統一語言中擷取。一旦準確地用統一語言描述出這些業務活動,我們就可以從語義相關性和功能相關性兩個方面識別業務邊界,進而提煉出初步的限界上下文。
從不同角度看待限界上下文,限界上下文會呈現出對不同對象的控制力。
領域邏輯層面:限界上下文確定了領域模型的業務邊界,維護了模型的完整性與一致性,從而降低系統的業務復雜度。
團隊合作層面:限界上下文確定了團隊的工作邊界,建立了團隊之間的合作模式,提升了團隊間的協作效率,“康威定律”告訴我們,系統設計(產品結構)等同組織形式,每個設計系統的組織,其產生的設計等同于組織之間的溝通結構,限界上下文指導產生的團隊結構的工作模式是最高效的。
技術架構層面:限界上下文確定了系統架構的應用邊界,保證了系統層和上下文領域層各自的一致性,建立了上下文之間的集成方式。微服務中,限界上下文指導技術人員劃分微服務的邊界,通常一個限界上下文作為一個在獨立進程中運行的微服務。
DDD驅動我們把每一個限界上下文設計成一個個“自治”的單元,自治要滿足四個特點:
最小完備是實現自治的基本條件,指的是自治單元履行的職責是根據業務價值的完整性和最小功能集進行設計的,這讓自治單元無需求助其他自治單元獲得信息,避免了不必要的依賴關系,同時也避免了不必要和不合適的職責添加到該自治單元上。
自我履行意味著由自治單元自身決定要做什么。是否應該履行某職責,由限界上下文擁有的信息來決定。站在自治單元的角度去思考:“如果我擁有了這些信息,我究竟應該履行哪些職責?”這些職責屬于當前上下文的活動范圍,一旦超出,就該毫不猶豫地將不屬于該范圍的請求轉交給別的上下文。自我履行其實意味著對知識的掌握,為避免風險,你要履行的職責一定是你掌握的知識范疇之內。
穩定空間指的是減少外界變化對限界上下文內部的影響。穩定空間符合開放封閉原則(OCP),即對修改是封閉的,對擴展是開放的,該原則其實體現了一個單元的封閉空間與開放空間。封閉空間體現為對細節的封裝與隱藏,開放空間體現為對共性特征的抽象與統一,二者共同確保了整個空間的穩定。
獨立進化指的是減少限界上下文的變化對外界的影響。用限界上下文的上下游關系來闡釋,則穩定空間寓意下游限界上下文,無論上游怎么變,我自巋然不動。要做到獨立進化,就必須保證對外公開接口的穩定性,因為這些接口被眾多消費者依賴和調用,一旦發生變更,就會牽一發而動全身。一個獨立進化的限界上下文,需要一個穩定、設計良好的接口設計,并在版本上考慮了兼容與演化。
最小完備是基礎,只有賦予了限界上下文足夠的信息,才能保證它的自我履行。穩定空間與獨立進化則一個對內一個對外,是對變化的有效應對,而它們又是通過最小完備和自我履行來保障限界上下文受到變化的影響最小。
2.1.2.2、上下文映射
限界上下文僅是一種對領域問題域的靜態劃分,還缺少一個重要的關注點,即:限界上下文之間是如何協作的?當我們發現彼此協作存在問題時,說明限界上下文的劃分出現了問題,也是識別限界上下文的一種驗證方法。Eric Evans 將這種體現限界上下文協作方式的要素稱之為“上下文映射(Context Map)”,并給出了9種上下文映射關系:
Open Host Service相當于微服務之間的協作關系;防腐層(Anti-Corruption)是一種高度防御性的策略,結合門面(Facade)模式和適配器(Adapter)設計模式,將模型與其需要集成的其他模型隔離開來,以防止被頻繁變更或不穩定的依賴模型污染和腐敗。
2.1.3、架構設計
“DDD不需要特殊的架構,只要是能將技術問題與業務問題分離的架構即可。” -- Eric Evans
傳統的三層架構分而治之、降低耦合、提高復用,但存在弊端,業務邏輯在不同層泄露,導致替換某一層變得困難、難以對核心邏輯完整測試。領域驅動設計給出了DDD分層架構、六邊形架構、整潔架構等分層架構,它們遵循“關注點分離”原則,旨在分離和隔離業務復雜度和技術復雜度,凸顯了領域模型,保證了領域模型的穩定性和一致性。
2.1.3.1、DDD分層架構
DDD分層架構將屬于業務邏輯的關注點放到領域層(Domain Layer)中,將支撐業務邏輯的技術實現放到基礎設施層(Infrastructure Layer)中,DDD創新性地引入了應用層(Application Layer),應用層扮演了兩重角色。一作為業務邏輯的門面(Facade),暴露了能夠體現業務用例的應用服務接口,又是業務邏輯與技術實現的粘合劑,實現二者之間的協作。下圖展現的是一個典型的領域驅動設計分層架構。藍色區域和業務邏輯相關,灰色區域與技術實現相關,二者涇渭分明,然后匯合在應用層。應用層確定了業務邏輯與技術實現的邊界,通過直接依賴或者依賴注入(DI,Dependency Injection)的方式將二者結合起來。
我們詳細介紹DDD分層架構中每一層的用意和設計:
表現層(User Interface Layer):負責向用戶顯示信息和解釋用戶命令,完成前端界面邏輯
應用層(Application Layer)
很薄的一層,負責展現層與領域層之間的協調,不包含任何的業務邏輯和業務規則,也不保留業務對象的狀態,是對領域服務的編排和轉發。應用層扮演了兩重角色。一作為業務邏輯的門面(Facade),暴露了能夠體現業務用例的應用服務接口,又是業務邏輯與技術實現的粘合劑,實現二者之間的協作。一個Application Service代表一個Use Case,一個Use Case代表了一個完整的業務場景,對于外部的客戶來說,應用層是與客戶協作的應用服務,接口代表是業務的含義。我們知道DDD分層架構的主要目標是分離業務復雜度與技術復雜度,應用層扮演的就是這樣的分界線。從設計模式的角度來理解,應用層的Application Service是一個Facade,對外部客戶,作為代表Use Case的整體應用,對架構內部,它負責整合領域層的領域邏輯與非業務相關的橫切關注點。
應用中,存在與具體的業務邏輯無關,在整個系統中會被諸多服務調用的橫切關注點實現,他們在職責上是內聚的,散布在所有代碼層次中,包括異常處理、事務、監控、日志、認證和授權等。所以與橫切關注點協作的服務應被定義為應用服務。
領域層(Domain Layer),是業務軟件的核心所在,也是軟件架構的核心,包含了業務所涉及的領域對象(實體、值對象)、領域服務,負責表達業務概念、業務狀態信息以及業務規則,具體表現形式就是領域模型。領域驅動設計提倡富領域模型,將業務邏輯歸屬到領域對象上。
基礎設施層(Infrastructure Layer):基礎層為各層提供通用的技術能力,包括:為應用層傳遞消息、提供 API 管理,為領域層提供數據庫持久化機制等。它還能通過技術框架來支持各層之間的交互。
2.1.3.2、整潔架構(Clean Architecture)
整潔架構中,同心圓代表應用軟件架構的不同部分,也是一種以領域模型為中心的架構,從里到外依次是Entities、Use Cases、Interface Adapters、Frameworks and Drivers。整潔架構明確了各層的依賴關系,越往里,依賴越低,越抽象,外圓代碼依賴只能指向內圓,內圓不知道外圓的任何事情。
2.1.3.3、六邊形架構(Hexagonal Architecture)
又稱為端口-適配器,六邊形架構也是一種分層架構,不是從上下或左右分,而是從內部和外部來分。六邊形架構在領域驅動設計和微服務架構設計中扮演了較重要的角色。六邊形架構將系統分為內部(內部六邊形)和外部,內部代表了應用的業務邏輯,外部代表應用的驅動邏輯、基礎設施(諸如REST,SOAP,NoSQL,SQL,Message Queue等)或其他應用,UI層、DB層、和各種中間件層實際上是沒有本質上區別的,都只是數據的輸入和輸出。內部通過端口和外部系統通信,端口代表了一定協議,以API呈現。一個端口對應多個適配器,對應多個外部系統,對這一類外部系統的歸納,不同的外部系統需要使用不同的適配器,適配器負責對協議進行轉換。六邊形架構有一個明確的關注點,一開始就強調把重心放在業務邏輯上,外部的驅動邏輯或被驅動邏輯存在可變性、可替換性,依賴具體技術細節。而核心的業務領域相對穩定,體現應用的核心價值。六邊形的六并沒有實質意義,只是為了留足夠的空間放置端口和適配器,一般端口數不會超過4個。適配器可以分為2類,“主”、“從”適配器,也可稱為“驅動者”和“被驅動者”。
代碼依賴只能使由外向內。對于驅動者適配器(也稱主適配器,Driving Adapter),就是外部依賴內部的。但是對于被驅動者適配器(也稱次適配器,Driven Adapter),實際是內部依賴外部,這時需要使用依賴倒置,由驅動者適配器將被驅動者適配器注入到應用內部,這時端口的定義在應用內部,但是實現是由適配器實現。
2.1.3.4、CQRS(命令與查詢職責分離)
CQRS使用分離的接口將數據查詢操作(Queries)和數據修改操作(Commands)分離開來,這也意味著在查詢和更新過程中使用的數據模型也是不一樣的,這樣讀和寫邏輯就隔離開來了。使用CQRS分離了讀寫職責之后,可以對數據進行讀寫分離操作來改進性能,可擴展性和安全。DDD 和 CQRS 結合,可以分別對讀和寫建模:
查詢模型是一種非規范化數據模型,不反映領域行為,只用于數據查詢和顯示。命令模型執行領域行為,在領域行為執行完成后通知查詢模型。如果查詢模型和領域模型共享數據源,則可以省略這一步;如果沒有共享數據源,可以借助于發布訂閱的消息模式通知到查詢模型,從而達到數據最終一致性。對于寫少讀多的共享類通用數據服務(如主數據類應用)可以采用讀寫分離架構模式。單數據中心寫入數據,通過發布訂閱模式將數據副本分發到多數據中心。通過查詢模型微服務,實現多數據中心數據共享和查詢。
通天塔從系統維度對數據庫進行了讀寫分離,通天塔的C端應用和服務大部分是讀場景,CMS是多寫應用,所以CMS的寫走主庫,讀服務按照使用場景不同訪問不同的從庫,實時請求、同步數據到集市、數據中心等,這點也從數據庫基礎架構上保證了通天塔系統的低延時和穩定。
2.1.3.5、綜述
六邊形架構的內部六邊形、DDD 分層架構的領域層和應用層、以及整潔架構 Use Cases 和 Entities 區域實現了核心業務邏輯。但是核心業務邏輯又由兩部分來完成:應用層和領域層邏輯。領域層實現了最核心的業務領域部分的邏輯,對外提供領域模型內細粒度的領域服務,應用層依賴領域層業務邏輯,通過服務組合和編排通過 API 網關向前臺應用提供粗粒度的服務。
業務需求變幻莫測,但我們總能在這些變化找出一些規律,用戶體驗、操作交互、以及業務流程的變化,往往只會導致UI層和流程的變化,總體來說,不管前端和外部如何變化,核心領域邏輯基本不會大變。把握好這個規律,我們就知道如何設計應用層和領域層,如何進行邏輯劃界了。架構模型正是通過分層方式來控制需求變化對系統的影響,確保從外向里受的影響逐步減小。面向用戶端的展現層可以快速響應外部需求進行調整和發布,靈活多變;應用層通過服務組合和編排實現業務流程的快速適配上線,以滿足不同的業務場景;領域層是經過抽象和提煉的業務原子單元,是非常穩定的。這些架構設計的好處是可以保證領域層的核心業務邏輯不會因為外部需求和流程的變動而調整,對于建立前臺靈活、中臺穩固的架構能力是很有好處的。下面是Herberto Graca的一張包含了六邊形、整潔、CQRS等架構的綜合圖,全面的說明了這些架構的設計要點和不同的出發點。
2.2戰術設計
戰略設計為我們提供一種高層視角來審視我們的軟件系統,而戰術設計則將戰略設計的成果具體化和細節化,它關注的是單個限界上下文內部技術層面的實施。DDD給我們提供了一整套技術工具集,包括實體、值對象、領域服務和資源庫等,如下:
2.2.1、行為飽滿的領域對象
讓我們先看幾個概念:
失血模型:是僅包含屬性的getter/setter方法的數據載體,沒有行為和動作,業務邏輯由服務層完成。
貧血模型:包括了屬性、getter/setter方法,和不依賴于持久化的原子領域邏輯,依賴于持久層的業務邏輯將會放到服務層中。
充血模型:包含了屬性、getter/setter方法、大部分的業務邏輯,包括依賴于持久層的業務邏輯,所以使用充血模型的領域層是依賴于持久層,服務層是很薄的一層,僅僅封裝事務和少量邏輯。
脹血模型:取消了Service層,脹血模型就是把和業務邏輯不相關的其他應用邏輯(如授權、事務等)都放到領域模型中。
脹血模型是顯而易見不可取的,這里不做過多討論。失血模型是絕大數企業開發應用的模式,一些火熱的ORM工具比如Hibernate,Entity Framework實際上助長了失血模型的擴散,而且傳統三層架構中的服務層,承受了太多的職責,如事務管理、業務邏輯、權限檢查等,這違反了單一職責原則和關注分離原則,并且產生了大量的依賴和循環依賴,當業務復雜度上升時,服務層所包含的代碼將會非常龐大和復雜,直接導致了維護成本和測試成本的上升。同時也會導致業務邏輯、狀態會散落到在大量方法中,原本的代碼意圖會漸漸不明確,我們將這種情況稱為由失血癥引起的失憶癥,它會導致系統變得愈發復雜和難以維護。
采用領域模型的開發方式,將數據和業務邏輯封裝在一起,從服務層移動到領域將業務邏輯模型中,這樣服務層可以只負責應用邏輯(事務、日志、認證、監控、編排等),領域模型可以專門負責其相關的業務邏輯,相關的業務分別內聚到不同的領域模型中,與現實領域的業務對象映射,一些很有可能重復的業務代碼都會被集中到一處,降低重復代碼,提升業務邏輯的復用、可測試性和維護性。貧血模型和充血模型都是滿足數據+行為的,應該采用哪種模式,大家這是一個爭論了曠日持久的問題,關注點還是在于領域模型是否要依賴持久層,我個人還是偏重于貧血模式,依賴持久層就意味著單元測試的展開要更加困難,而且領域對象的生命周期應該交給外部模型才更合理。
2.2.2、領域驅動設計元模型
2.2.2.1、實體(Entity)
實體是一種具有唯一身份標識的對象,具有持續的生命周期,除唯一標識其他屬性是可變的。實體通過它的唯一標識被區分。例如實體訂單Order,標識為oderId,通天塔的活動實體Activity,標識為activityId。
2.2.2.2、值對象(Value Object)
當我們只關心一個模型元素的屬性時,應把它歸類為值對象。應該使這個模型元素能夠表示出其屬性的意義,并為它提供相關功能。建議將值對象設計成一個不變(Immutable)對象,這樣就不需要擔心并發帶來的諸如同步、沖突等問題了,這既降低了編程的難度,又可以無需引入額外的同步鎖影響程序的性能。也不要為它分配任何標識,這樣應用也無需去管理值對象的生命周期。值對象通過比較其屬性(equals)區分是否是相同值對象。應該盡量使用值對象來建模而不是實體對象。
在領域驅動設計中,提倡盡量定義值對象來替代基本類型,因為基本類型無法體現統一語言中的領域概念。假設一個實體定義了許多屬性,這些屬性都是基本類型,就會導致與這些屬性相關的領域行為都要放到實體中,導致實體的職責變得不夠單一。引入值對象后情況就不同了,我們可以利用合理的職責分配,將這些職責(領域行為)按照內聚性分配到各個值對象中,這個領域模型就能變得協作良好。
值對象可以與其所在的實體對象保存在同一張表中,值對象的每一個屬性保存為一列;值對象也可以獨立于其所在的實體對象保存在另一張表中,值對象獲得委派主鍵,該主鍵對客戶端是不可見的。
2.2.2.3、聚合(Aggregate)
聚合中所包含的對象之間具有密不可分的聯系,一個聚合中可以包含多個實體和值對象,因此聚合也被稱為根實體。聚合是持久化的基本單位,它和資源庫具有一一對應的關系。在聚合中,根是唯一允許外部對象保持對它的引用的元素,而邊界內部的對象之間則可以互相引用。除根以外的其他Entity都有本地表示,但這些標識只有在聚合內部才需要加以區別,因為外部對象除了根Entity之外看不到其他對象。在一個聚合中直接引用另外一個聚合并不是DDD所鼓勵的,但是我們可以通過ID的方式引用另外的聚合,
聚合是一個事務的邊界。如果一次業務操作涉及到了對多個聚合狀態的更改,那么應該采用發布領域事件(參考下文)的方式通知相應的聚合。此時的數據一致性便從事務一致性變成了最終一致性(Eventual Consistency)。
2.2.2.4、領域服務(Domain Service)
建模一個領域概念,把它放在實體上不合適,它放在值對象上也不合適,或者碰到跨聚合實例業務邏輯,沒辦法合理放到某個實體中的業務邏輯,領域服務就是應對這些情況的服務。如果勉強地把這些重要的領域功能歸為Entity或Value Object的職責,那么不是歪曲了基于模型的對象的定義,就是人為地增加了一些無意義的對象;領域服務和上文中提到的應用服務是不同的,領域服務是領域模型的一部分,而應用服務不是。應用服務是領域服務的客戶,它將領域模型變成對外界可用的軟件系統。如果將太多的領域邏輯放在領域服務上,實體和值對象上的業務邏輯會越來越弱,將變成貧血對象。
在分層架構中要區分什么時候應該定義領域服務,什么時候應該定義應用服務,一個根本的判斷依據是看需要封裝的職責是否與領域相關。
2.2.2.5、資源庫(Repository)
資源庫用于保存和獲取聚合對象,將實際的存儲和查詢技術封裝起來,對外隱藏封裝了數據訪問機制。只為那些確實需要直接訪問的聚合提供Repository。讓客戶始終聚焦于模型,而將所有對象的存儲和訪問操作交給Repository來完成。資源庫與DAO有些相似,但也存在顯著區別,DAO是比Repository更低的一層,同時DAO只是對數據庫的一層很薄的封裝,而資源庫則更加具有領域特征,以“領域”為中心,所描述的是“領域語言”。另外,所有的實體都可以有相應的DAO,但并不是所有的實體都有資源庫,只有聚合才有相應的資源庫。
2.2.2.6、領域事件(Repository)
在Eric的《領域驅動設計》中并沒有提到領域事件,領域事件是最近幾年才加入DDD生態系統的。在傳統的軟件系統中,對數據一致性的處理都是通過事務完成的,其中包括本地事務和全局事務。DDD的一個重要原則便是一次事務只能更新一個聚合實例,但存在一個業務流程涉及修改多個聚合的事務,怎么實現整個業務流程的數據一致性呢?在DDD中,領域事件便可以用于處理上述問題,此時最終一致性取代了事務一致性,通過領域事件的方式達到各個組件之間的數據一致性。既然是領域事件,他們便應該從領域模型中發布,一個領域事件是指一個在領域中“有意義”的事件。領域事件的最終接收者可以是本限界上下文中的組件,也可以是另一個限界上下文。再進一步發展,事件驅動架構可以演變成事件源(Event Sourcing),即對聚合的獲取并不是通過加載數據庫中的瞬時狀態,而是通過重放發生在聚合生命周期中的所有領域事件完成。
2.2.2.7、工廠(Factories)
當創建一個對象或創建整個聚合時,如果創建工作很復雜,或者暴露了過多的內部結構,則可以使用Factory進行封裝,應該將創建復雜對象的實例和聚合的職責轉移到一個單獨的對象,這個對象本身在領域模型中可能沒有職責,但它仍是領域設計的一部分。
2.2.2.8、模塊(Modules)
可以從兩種維度來觀察模型,一是可以在Module中查看細節,而不會被整個模型淹沒;二是觀察Module之間的關系,而不考慮其內部細節。模塊之間應該是低耦合的,而在模塊內部則是高內聚的。模塊并不僅僅是代碼的劃分,而且也是概念的劃分。找到一種低耦合的概念組織方式,從而可以相互獨立地理解和分析這些概念。對模型進行精化,直到可以根據高層領域概念對模型進行劃分,同時相應的代碼也不會產生耦合。
2.2.2.9、模型關系
2.2.3、對象概念
VO(View Object):視圖對象,用于展示層,它的作用是把某個指定頁面(或組件)的所有數據封裝起來。
DTO(Data Transfer Object)
數據傳輸對象,分布式應用提供粗粒度的數據實體,也是一種數據傳輸協議,以減少分布式調用的次數,從而提高分布式調用的性能和降低網絡負載,這里泛指用于展示層與服務層之間的數據傳輸對象。RPC對外暴露的服務涉及對象API就是DTO,如JSF(京東RPC框架)、Dubbo。
對比VO:絕大多數應用場景下,VO與DTO的屬性值基本一致,但對于設計層面來說,概念上還是存在區別,DTO代表服務層需要接收的數據和返回的數據,而VO代表展示層需要顯示的數據。
DO(Domain Object):領域對象,就是從現實世界中抽象出來的有形或無形的業務實體。DO不是簡單的POJO,它具有領域業務邏輯。
PO(Persistent Object):持久化對象。
對比DO:DO和PO在絕大部分情況下是一一對應的,但也存在區別,例如DO在某些場景下不需要進行顯式的持久化,只駐留在靜態內存。同樣PO也可以沒有對應的DO,比如一對多表關系在領域模型層面不需要單獨的領域對象。
下面是這些對象在系統架構中的分布:
2.2.4、Domain Primitive
Domain Primitive是一個在特定領域里,擁有精準定義的、可自我驗證的、擁有豐富行為和業務邏輯的Value Object,DP使用業務域中的原生語言,可以是業務域的最小組成部分、也可以構建復雜組合。Domain Primitive是Value Object的進階版,在原始VO的基礎上要求每個DP擁有概念的整體,而不僅僅是值對象。在VO的Immutable基礎上增加了Validity和行為。
在項目中,散落在各個服務或工具類里面的代碼,都可以抽出來放在DP里,成為DP自己的行為或屬性。原則是:所有抽離出來的方法要做到無狀態,比如原來是static的方法。如果原來的方法有狀態變更,需要將改變狀態的部分和不改狀態的部分分離,然后將無狀態的部分融入DP。因為DP也是一種Object Value,本身不能帶狀態,所以一切需要改變狀態的代碼都不屬于DP的范疇。Domain Primitive涉及三種手段:
讓隱性的概念顯性化(Make Implicit Concepts Explicit)
通天塔活動類型就是一個簡單的int類型,屬于隱式概念,但活動類型包含了很多相關業務邏輯,比如類型名稱,不同類型活動具有獨特的Icon,判斷活動類型是否是判斷等,我們把活動類型顯性化,定義為一個Value Object。
讓隱性的上下文顯性化(Make Implicit Context Explicit)
當要實現一個功能或進行邏輯判斷依賴多個概念時,可以把這些概念封裝到一個獨立地完整概念,也是一種Object Value:
封裝多對象行為(Encapsulate Multi-Object Behavior)
常見推薦使用Domain Primitive的場景有:
有格式要求的String:比如Name,PhoneNumber,OrderNumber,ZipCode,Address等。
限制的Integer:比如OrderId(>0),Percentage(0-100%),Quantity(>=0)等。
可枚舉的int:比如Status(一般不用Enum因為反序列化問題)。
Double或BigDecimal:一般用到的Double或BigDecimal都是有業務含義的,比如Temperature、Money、Amount、ExchangeRate、Rating等。
復雜的數據結構:比如Map<String, List<Integer>>等,盡量能把Map的所有操作包裝掉,僅暴露必要行為,如通天塔的活動Map類。
接口變得清晰可讀,校驗邏輯內聚,在接口邊界外完成,無膠水代碼,業務邏輯清晰可讀,代碼變得更容易測試,也更安全。
最后
DDD不是一套框架,而是一種面向復雜問題的建模方法論和實踐,所以在代碼層面缺乏了足夠的約束,導致DDD在實際應用中上手門檻很高,甚至可以說絕大部分人都對DDD的理解有所偏差。而且DDD諸多實踐在真正踐行時面臨很多挑戰,首先是領域專家和技術人員在建模過程中要摒棄自己固有的專業背景和思維定式,專注于問題域,基于統一語言緊密溝通和協作,具有深度業務領域理解和洞察的領域專家和一個精通領域建模和架構設計的技術團隊一樣少見,都必須經過長時間學習和實踐的,其次技術人員必須轉變思維和架構習慣,軟件系統最終交付的是業務價值,不是功能和技術方案,一切要以問題和業務為核心去建模和架構。通天塔后端團隊在高并發和高性能應用構建方面有著非常豐富的經驗,但在DDD實踐和享受到它的巨大價值層面我們還是剛起步,千里之行始于足下,我們正在邁出堅實一步,后續我們也會出DDD通天塔實踐篇,講述我們的經驗和心得。
參考鏈接:
[1] 圖書《領域驅動設計》: https://book.douban.com/subject/5344973/
[2] 當中臺遇上DDD:https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw
[3] 張逸系列文章:http://zhangyi.xyz
總結
以上是生活随笔為你收集整理的在战略设计层面提出了域、子域、限界上下文等重要概念;在战术设计层面提出了实体、值对象、领域服务、领域事件、聚合、工厂、资源库等重要概念。 领域驱动设计 有防腐层的设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt::WA_TransparentFo
- 下一篇: Wireshark实验——IP 协议