您好,登錄后才能下訂單哦!
這篇文章主要介紹“對領域驅動設計的認識有哪些”,在日常操作中,相信很多人在對領域驅動設計的認識有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”對領域驅動設計的認識有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一口吃不成胖子,一朝減不成瘦子。當前服務的開發遵循的都是controller、service、dao的模式,業務邏輯都在service中實現,雖有運用工廠、策略、適配器等設計模式,但依舊是面向對象下的過程式編程。相較于過程式的編程,面向對象的編程則對抽象有了更高的要求。完成思維模式的改變不是一朝一夕就可以的,需要一個循序漸進的過程。
將一個現有的服務系統做領域劃分,簡單的可以從服務提供的接口出發。首先對服務提供的接口進行劃分,可以粗粒度的劃分出應該有的領域,而且接口的現有上下文可以粗略的等同于服務領域應該有的限界上下文,再向下對領域中的每一個接口進行分析,提取其中的實體、值對象,最終在領域內容找出聚合根,并提煉出相應的工廠與領域服務。分析至此,領域化改造基本可以動手開始實施了。但此時的理論基礎還是粗粒度的,在實施的過程中肯定會發現一些不合適的地方。
欲要成其事,必先利其器。為了實施過程中能順利一些,可以在初步劃分好領域之后,對領域進行組內的溝通,一方面是成員之間信息互通,統一領域專業術語;另一方面可以集思廣益對領域的劃分進行進一步的完善與細化。
領域就是對現實中一系列或一組問題的抽象及其“解”,如同一道完整的數學大題一樣,有完整的題干(對現實問題的抽象描述)和解答過程。領域的概念是一種虛擬的概念,領域更多的框定出一系列或者一組抽象的內容或者說叫事物,在領域中解決問題需要依賴于界限上下文將問題限定在一定的范圍之中。
限界:解決問題要在一定的界限范圍內才能高效、準確的解決問題,若不能圈定一個范圍,會將問題無限放大,所有的解決方案都會被各種條件限制。 上下文:即包含處理邏輯,包含邏輯處理過程中的數據狀態。上下文存在于領域中,為解決領域中的特定問題框定一個范圍,并且為解決當下的問題提供一個可以依賴的上下文。上下文的意思即為提供一個環境,在領域設計中就是為當前的動作提供一個環境。在一個上下文中會有多個動作,在每一個動作前提供當前動作所需的準備動作,在之后提供當前動作結果的后續處理,以此組成一個上下文。
要理解上下文,可以從現有的編程語言、系統設計中類比理解。先從一個點出發,再到一個面。
在前端HTML語言中,整個HTML文檔和CSS樣式、JS腳本共同構成了一個上下文,一個DIV標簽代表的內容要在頁面中如何顯示,要依賴于其父節點的位置,以及為當前DIV設置的CSS樣式和從父標簽那繼承來的CSS樣式,以及對當前DIV指定的JS腳本,共同影響得到。這里的“父節點位置”、“為當前DIV設置的CSS樣式和從父標簽那繼承來的CSS樣式”、“ 對當前DIV指定的JS腳本”就是所謂的上下文。甚至于說整個DIV內部的自節點有什么內容或影響也是上下文的一部分。
在Spring中,也存在這上下文的概念。Spring中的上下文,即包含當前的運行環境,還有當前Spring可以掃描到的配置、bean等信息。當spring中獲得或者初始化一個bean的時候,要明確當前的運行環境(是web環境還是本地應用),要明確是否與相關的配置或者約定會影響到bean的初始化,要明確初始化當前bean的依賴,要明確bean創建出來后有沒有要執行的操作(默認執行的方法等),是否有其他的bean的初始化需要依賴當前bean。這里的“運行環境”、“配置、bean信息”、“依賴”、“初始化后的方法”、“被依賴”就共同組成來上下文。
實體通俗的表現即為一個bean.java文件,而且是一個充血的模型,其中有屬性(唯一標識及其他字段),為實體賦予實際意義的動作(動作:工程表現就是Java文件中的方法)。實體中方法的命名一定是可以描述實際意義的動作描述,描述的出發角度就是以當前這個實體為第一人稱視角,描述當前動作的實際意義。
因為實體為充血的模型,其中既有方法也有屬性值,所以實體是有狀態的對象,再使用Spring容器提供的單例模式已經不合適了,會引起嚴重的線程安全問題,所以要使用其他方法進行初始化。可以像傳統設計中的數據庫值映射對象一樣在線程中的new出來,也可以使用工廠類生產出來,也可以使用Spring容器的原型模型提供,但是提供出來的對象是空的對象,所有的值都需要手動傳入。
實體的一個重要且必須的字段就是——唯一標識。所以在實體對象初始化時,唯一標識是一個重要的字段,(雖然很重要,但是也是可以為空的,比如在創建新的實體的時候。)剩余的其他字段,應按需加載,當需要使用的時候再創建或恢復(從基礎設置中獲得),這樣做的重要的一點兒就是性能優化,節約資源。
重要一點:實體的定義是以業務領域設計為出發點,其并不與數據庫中的表(或者說是數據庫中的表在工程中的映射對象)是一一對應的,嚴格來說是不能一一對應的,但處于現階段現狀的考慮,允許將實體與數據庫中的表進行對應。但進行對應后就會引入一個新的問題:實體與數據庫表映射對象會區分不清楚,造成設計思想理解上的障礙。
值對象就是一個沒有唯一標識的實體,但也是有狀態的,所以也不適用于Spring容器的單例模式,而更適用于使用new的方法在需要的時候手動創建出來。值對象要做到盡量的簡單,具有實際業務意義的動作(即class文件中的方法),應盡可能的都放置到Entity或領域服務中,值對象中應最多只保留必要的數據校驗的,且作用域也要局限在當前值對象內部,不要使用外部的值、變量等,但這不代表不能讓值對象有動作表現,為了完成一個業務需求而執行一個動作,同時又沒有一個實體、聚合根可以描述(定義)當前動作的時候,就應該由值對象來描述動作。
再領域改造的過程中,當一個“動作”已經明確無法由某個實體來描述,但有沒有扎實的論據來證明這個“動作”可以由一個值對象來描述的時候,就先將這個動作交由領域服務來描述,直到隨著系統的迭代演進有了足夠的論據可以支持由某個值對象來描述這個動作,再將動作的描述從領域服務轉移到值對象中。
聚合,就是將業務領域中具有強依賴關聯性的實體、值對象進行組合,是一個高級抽象的概念。
對于聚合的簡單、具像化理解可以理解為一個“鎖”,Synchronize、lock是程序代碼執行的鎖,事務是數據庫數據持久化的鎖,聚合就是業務系統架構設計的鎖。將在業務領域中有強依賴關系的對象歸集到聚合根下,使用聚合根作為“鎖對象”,所有的動作都從聚合根出發并在聚合根結束,保證業務領域設計的“原子性”。
但領域驅動設計中的聚合不僅僅是一個鎖的概念,聚合并沒有強制性的一致性,終其所有是代表了領域中的實體、值對象的組合。
聚合根可以為一個單獨的java文件,也可以是一類特殊的Entity。前者是不建議的方案,后者也是通常的做法,即將一個關鍵的Entity作為當前領域的聚合根,此時聚合根也就具有類屬性和賦予類實際意義的“動作”。使用一個關鍵的Entity作為聚合根,則此聚合根也就具有的狀態,有狀態的對象初始化是需要成本代價的,所以在某些簡單場景可以允許領域服務等直接訪問聚合根下的其它Entity,但再涉及到跨領域的業務處理時應使用聚合根進行處理,不適用于直接訪問聚合根下其它Entity的情況。
當聚合根是一類特殊的Entity的時候,聚合根就是一個充血的即有屬性又有實際意義動作的對象。實體(或者以實體作為載體的聚合根)中的動作應為僅完成一項動作,不要將多個動作合并到一個方法中,除非這個動作在業務領域設計中也是“原子性”的,否則在實體實現中應拆分為多個動作。
實體的每一個動作只完成一個“原子性”的行為,當一個業務操作需要對一個實體進行一連串的動作時,就要依靠領域服務,將實體提供的一系列“原子性”的動作封裝為一個獨立的動作,并暴露出去。對于領域外部僅僅是一個動作(即為一個方法的調用),但對于領域內的實體就是一連串的“原子”動作的集體表現。對于這個封裝的過程,就已經將限界上下文的思想體現出來了,但這不是限界上下文的全部,在領域服務的這個方法之外,還會有別的邏輯,比如說操作日志的記錄等,這些都是限界上下文的內容。
對于領域服務的方法,就要進行相應的事務控制。
領域事件在理解上可以分為兩類:一類是抽象意義上的“事件”,在系統設計中有列出,但是不對應系統開發中的任何的類、組件、框架、中間件等實體的,是一種在設計層面de抽象的,偏向于領域專家的理解;另一種就是可以體現在組件、中間件上的“事件”,是有型的,開發人員可以實實在在看得到并能通過技術手段進行控制的,偏向于開發人員的理解。
前一種“事件”可以以后一種“事件”為載體在系統開發種體現出來,但也可以使用非后者的形式在系統中體現出來:可能就是系統中的一段邏輯。
業務邏輯處理中,Entity的動作處理的數據(即為Entity的屬性的值)可以來自于入參,也可能是從某個服務或者某個持久化容器(緩存、數據庫等)中拿到的,將這些服務的接口在領域中的映射|代理|實現統統劃入基礎設施,意為為領域提供基礎支持。
基礎設施的接口是由領域定義的,基礎設施實現接口的定義為領域提供基礎服務。基礎設施在實現領域定義的接口時,應遵守領域中相關接口的約定,并以技術手段對保證約定的正常履行。例如:在將修改實體的狀態更新為狀態B時,領域的接口約定必須要在狀態為A的情況下才能變更,從保證數據安全與效率的考量下,基礎設施要保證接口相關約定的正常履行。
防腐層的設計意義就是為了保持領域內部設計的內聚性,不讓領域外部的改變影響到領域內部的實現,使領域內部變得混亂,變得腐敗。所以這一層換一個理解就是:為領域設計的適配器。 11.工廠-Factory 工廠作為直接的生產對象,用來完成各種實體、值對象、聚合根的初始化構建工作,既能提供單個對象的初始化構建,又能勝任批量對象的初始化構架。但是工廠除了對象的初始化構建之外不應該承擔任何其他的動作,如果這項動作與對象的初始化構建無關,既是再小也不能侵入到工廠內,不然會使得工廠越來越腐爛,只到最后不能再稱之為“工廠”。
工廠應該是以單例的形式存在,但是工廠生產出來的對象:實體、值對象、聚合根應該是模版模式生產出來的完全獨立的對象。
實體與值對象的劃分,在直覺上就是有無“唯一標識”,但這也不是唯一的標準。如果嚴格按照此一個唯一的標準進行劃分也無可厚非,但實際開發中總有各種情況需要“妥協”,特別是現有的舊項目進行領域化改造的時候,這樣的掣肘限制就更多一些。
可以將值對象簡單的歸結為貧血的且無唯一標識對象,如果一個對象,它有一個字段可以作為唯一標識,但是在系統中不會頻繁的發生變化,類似于枚舉,但又不是枚舉,可能是在配置文件中進行配置在運行時動態生成的一些標識符對象,且又具有一定的“行為”,看起來很模糊,即可以定義為實體,也可以定義為值對象。這種情況下“有無‘唯一標識’”就不是劃分實體與值對象的唯一標準了,而是從實際情況出發,辯證的進行劃分。
基于現有的技術、組件與解決方案,數據的持久化還是離不開數據庫,即使是NoSQL的新型的數據庫,也會涉及到原子性的問題,由此在系統設計中永遠離不開“事務”這樣一個原子性集合操作。
領域設計中的事務邊界,不應該超出一個“聚合根”,如果超出了一個“聚合根”的范圍,則需要考慮聚合的是否合理,是不是少了聚合?或者是不是某個實體或值對象加入了錯誤的聚合根?如果聚合是合理的,那就考慮是否可以使用領域事件進行解耦?若使用領域事件進行解耦操作,帶來的問題就是“以最終一致性代替了事務一致性”,只能算是一個后備的方案。若要保證強的事務的一致性,將事務添加到領域服務甚至是接口級別,也是一種用無可用的折衷方案。
與“實體與值對象的劃分”中提到的一樣,具體的劃分還是要以標準規范為依據,從實際情況出發,辯證的進行事務邊界的劃分。
領域驅動設計的重點在“設計”而非“領域”,劃分領域是手段,實現設計才是目的。領域設計中重要的參與角色有“領域專家”、“開發人員”,應該還包含一個“中間人”。為什么使用“中間人”這個詞?在多數公司中,產品經理其實擔任了“領域專家”的職能,同時將真正的領域專家——運營、銷售、售后等人員——對開發人員隱藏了。開發人員所能接收到的領域專業知識大多都是經過產品經理轉述的,所以該作為“中間人”的產品經理前侵成了“開發人員”所面對的“領域專家”。所以就將“中間人”的角色給弱化掉了,經過產品經理的轉述,開發人員所理解的領域知識多多少少會有些失真。 要解決這個問題無非三個方法:
需求溝通時,業務、產品、研發一起參與
產品經理成為真正的領域專家
產品經理了解研發的設計
第一個方案不是最優的方案,越多人發言的會就越低效,這會產生更多的時間等成本;第二個方案,就是對產品經理提出了更高的要求,要產品經理深切的參與到運營活動中去,并且要理解未來的業務發展方向;第三個方案就要求產品經理要對研發人員的具體實現有一定的了解,不需要了解到每一個方法、每一行代碼,但要了解到系統中有哪些對象、都提供了哪些功能,這對應到領域設計,就是要求產品經理要了解工程中有哪些實體等對象,提供了哪些領域服務,各個領域之間是如何協同工作的。
到此,關于“對領域驅動設計的認識有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。