[譯文]Domain Driven Design Reference(三)—— 模型驅動設計的構建模

本書是Eric Evans對他自己寫的《領域驅動設計-軟件核心複雜性應對之道》的一本字典式的參考書,可用於快速查找《領域驅動設計》中的諸多概念及其簡明解釋。

 

 

其它本系列其它文章地址:

[譯文]Domain Driven Design Reference(一)—— 前言

[譯文]Domain Driven Design Reference(二)—— 讓模型起作用

[譯文]Domain Driven Design Reference(三)—— 模型驅動設計的構建模塊

 

 

Ⅱ.模型驅動設計的構建模塊

  這些模式根據領域驅動設計,廣泛地推行了面向對象設計的最佳實踐。他們指導決策來提煉模型,並使模型和實現保持一致,每一個都增強了其他的有效性。仔細制定模型元素的細節爲開發人員提供了一個穩定的平臺,從中可以探索模型並使其與實現保持緊密聯繫。

 

分層架構

  在面向對象的程序中,用戶界面,數據庫和其他支持代碼通常會直接寫入業務對象。額外的業務邏輯被嵌入在UI部件和數據庫腳本的行爲中。發生這種情況是因爲在短期內,這樣做是最簡單的方法。

  當與領域相關的代碼通過如此大量的其他代碼被擴散時,變得非常難以理解和推理。UI的表面變化實際上可以改變業務邏輯。要更改業務規則,可能需要仔細跟蹤UI代碼,數據庫代碼或其他程序元素。實現一致的、模型驅動的對象變得不切實際。自動化測試變得難以進行。由於每個活動都涉及到所有的技術和邏輯,程序必須保持非常簡單,否則就無法理解。

  因此:

  隔離領域模型和業務邏輯的表達形式,並消除對基礎架構,用戶界面甚至非業務邏輯的應用程序邏輯的依賴。將一個複雜的程序分成多個層。在每個層次內開發一個內聚的設計,並且僅依賴於下面的層。遵循標準的建築模式,爲上面的分層提供鬆散的耦合。將所有與領域模型相關的代碼集中在一個層中,並將其與用戶界面,應用程序和基礎設施的代碼隔離。領域對象沒有顯示自己,存儲自己,管理應用程序任務等等的職責,可以集中在表達領域模型上。這使得一個模型能夠發展到足夠豐富,足夠清晰,能夠捕獲必要的業務知識並將其付諸實踐。

  這裏的關鍵目標是隔離。 諸如“六邊形架構”之類的相關模式可以起到允許我們的領域模型表現避免依賴和引用其他系統問題,甚至更好的效果。

 

實體

  許多對象代表了一個連續的具有身份標識的主線,貫穿整個生命週期,儘管其屬性可能會改變。一些對象不是主要由它們的屬性定義的。它們代表了貫穿時間並經常跨越不同展現形式的主線的身份標識。有時這樣的對象必須與另一個對象匹配,即使屬性不同。錯誤的身份可能導致數據損壞。

  因此:

  當一個對象被它的身份而不是它的屬性所區分時,把它作爲它在模型中定義的要點。保持簡單的類定義,並關注生命週期的連續性和身份標識。

  定義一個區分每個對象的方法,而不管它的形式或歷史。 對通過屬性調用匹配對象的需求保持警惕。定義一個保證爲每個對象產生唯一結果的操作,可能通過附加一個保證唯一的符號。這種標識手段可能來自外部,也可能是由系統創建的任意標識符,但必須符合模型中的身份標識區別。

  模型必須定義什麼是同樣的事情。

  (又稱參考對象)

 

值對象

  有些對象描述或計算事物的一些特徵。

  許多對象沒有概念上的身份標識。

  跟蹤實體的身份標識至關重要。但將身份標識附加到其他對象可能會傷害系統性能,增加分析工作,並使所有對象看起來都一模一樣。軟件設計是一個複雜的持續戰鬥。我們必須作出區分,以便只有在必要時才進行特殊處理。

  然而,如果我們把這種類型的對象看作是缺少身份的話,那麼我們並沒有在我們的工具箱或詞彙中添加太多東西。實際上,這些對象具有自己的特點,對模型本身也有意義。 這些是描述事物的對象。

  因此:

  當您只關心模型元素的屬性和邏輯時,將其歸類爲值對象。使其表達它傳達的屬性的含義並賦予它相關的功能。將值對象視爲不可變的。使所有操作是不依賴任何可變狀態的無副作用函數。不要給值對象任何身份標識,並避免保留實體所必需的設計複雜性。

 

領域事件

  領域專家關心的事情發生了。一個實體負責跟蹤其狀態和規定其生命週期的規則。但是,如果你需要知道狀態變化的實際原因,這通常是不明確的,並且可能很難解釋系統如何實現它。審計線索可以允許跟蹤,但通常不適合用於程序本身的邏輯。實體的變化歷史可以允許訪問先前的狀態,但忽略這些變化的含義,以便對信息的任何操作都是程序性的,並且經常被拋出領域層。

  分佈式系統中出現了一系列獨特但又相關的問題。分佈式系統的狀態在任何時候都不能保持完全一致。我們始終保持聚合內部一致,而異步的進行其他更改。當更改在網絡的節點間傳播時,可能很難解決無序或來自不同來源的多個更新。

  因此:

  將關於領域中活動的模型信息視爲一系列離散事件。將每個事件表示爲一個領域對象。這些不同於系統事件,它們反映了軟件本身的活動,雖然通常系統事件與領域事件相關聯或者作爲領域事件的響應的一部分,或者作爲將領域事件的信息攜帶到系統中的一種方式。

  領域事件是領域模型的一個完整的部分,是領域中發生的事情的表示形式。忽略不相關的領域活動,同時明確領域專家想要跟蹤或者被通知的事件,或者與其他模型對象中的狀態改變相關聯的事件。

  在分佈式系統中,實體的狀態可以從特定節點的當前已知的領域事件中推斷出來,從而在沒有關於整個系統的完整信息的情況下得到相關的模型。

  領域事件通常是不可變的,因爲它們是過去的某種事物的記錄。除了對事件的描述之外,領域事件通常包含事件發生時間的時間戳以及事件涉及的實體的身份標識。此外,領域事件通常具有單獨的時間戳,指示事件何時進入系統以及使其進入系統的人的身份標識。如果有用,領域事件的身份標識可以基於這些屬性的一些集合。所以,例如,如果同一個事件的兩個實例到達一個節點,則它們可以被識別爲相同的。

 

服務

  有時候,這不是一回事。領域的一些概念由模型作爲對象是不自然的。強制所需的領域功能成爲實體或者值對象的職責,要麼篡改基於模型的對象的定義,要麼添加無意義的虛擬對象。

  因此:

  當領域中的重要流程或轉換不是實體或值對象的自然職責時,添加一個操作到模型中作爲一個單獨的接口同時聲明爲一個服務。定義一個服務契約,一組關於與服務交互的聲明。用一個特定限界上下文的通用語言來陳述這些聲明。給服務一個名字,這也成爲通用語言的一部分。

 

模塊

  每個人都使用模塊,但很少將它們視爲模型的完整部分。代碼被分解成各種類別,從技術架構的各個方面到開發人員的工作任務。即使是做了很多重構的開發人員也傾向於使用項目早期構思的模塊。

  耦合和凝聚力的解釋傾向於使它們聽起來像是技術指標,根據關聯和相互作用的分佈進行機械的判斷。然而,這不僅僅是將代碼劃分爲模塊,還包括概念。一個人一次可以思考多少事情是有限的(因此耦合度低),不連貫的想法片段很難被理解爲一個無差別的想法(因此具有很高的內聚性)。

  因此:

  選擇能夠講述系統故事的模塊,幷包含一系列內聚的概念。讓模塊名稱成爲通用語言的一部分。模塊是模型的一部分,它們的名稱應反映對領域的洞察。

  這通常會導致模塊之間的低耦合,但是如果它不尋找一種方法來改變模型來分解概念,或者是一個被忽視的概念,它可能是一個能夠以有意義的方式將元素組合在一起的模塊的基礎。在可以被獨立地理解和推理的概念上尋求低耦合。根據高層領域概念對模型進行細化直到它被劃分,並將相應的代碼解耦。

 

聚合

  要保證複雜關聯模型中對象變化的一致性是很困難的。他們能夠被是概念上的構成部分的其它對象的變化所掩蓋。在多個服務器之間分發對象或設計異步事務時會出現類似的問題。

  因此:

  將實體和值對象集中到聚合中並在周圍定義邊界。選擇一個實體作爲每個聚合的根,並允許外部對象僅保留對根的引用(對內部成員的引用僅在一個操作中返回出去才能使用)。定義聚合的屬性和不變量作爲一個整體,並將這個約束的責任賦予根【這裏指的是聚合根】或某種指定的框架機制。

  使用相同的聚合邊界來管理事務和分配。

  在一個聚合邊界內,同步地應用一致性規則。 跨越邊界,異步地處理更新。

  在一臺服務器上共同維護一個聚合。允許不同的聚合在節點間分配。

  如果這些設計決策沒有受到聚合邊界的良好指導,請重新考慮模型。是領域的場景正在暗示着一個重要的新見解嗎?這種改變通常會提高模型的表現力和靈活性,並解決事務和分配問題。

 

倉儲

  查詢通用語言表達的聚合。

  可遍歷的關聯的擴散只用於找到弄亂模型的東西。在成熟模型中,查詢經常表達領域概念。然而查詢可能會導致問題。

  應用大多數數據庫訪問基礎架構的純粹技術複雜性迅速吞噬了客戶端代碼,導致開發人員陷入了領域層,使得模型無關緊要。

  查詢框架可能會封裝大部分的技術複雜性,使開發人員能夠以更自動化或聲明的方式從數據庫中提取所需的確切數據,但這隻能解決一部分問題。

  不受約束的查詢可能會從對象中拉出特定的字段,違反封裝,或從聚合內部實例化幾個特定的對象,讓聚合根變得充滿變數並使這些對象無法執行領域模型的規則。領域邏輯移入查詢和應用程序層代碼,實體和值對象變成僅僅爲數據容器。

  因此:

  對於需要全局訪問的每種聚合類型,創建一個服務,它可以提供所有聚合根類型的對象的在一個內存集合中的錯覺。通過一個大家都知道的全局接口設置訪問。提供添加和刪除對象的方法,這將封裝實際數據往數據存儲中的插入或刪除。提供基於對領域專家有意義的標準來選擇對象的方法。返回完全實例化的對象或屬性值符合條件的對象集合,從而封裝實際的存儲和查詢技術,或者返回給予以惰性的方式完全實例化的聚合的幻覺的代理。僅爲實際需要直接訪問的聚合根提供倉儲。保持應用程序邏輯專注於模型,委託所有的對象存儲和訪問給倉儲。

 

工廠

  當創建一個完整的,內部一致的聚合或者一個大值對象變得複雜或者顯示太多的內部結構時,工廠提供封裝。一個對象的創建本身可以是一個主要的操作,但是複雜的組裝操作不適合由創建的對象來承擔。將這些職責結合起來可能會產生難以理解並且難看的設計。讓客戶端直接組裝會混亂客戶端的設計,破壞組裝對象或集合的封裝,並且過度地將客戶端耦合到所創建對象的實現中【舉個例子,這裏的客戶端可以理解成應用層或者UI層】。

  因此:

  將創建複雜對象和聚合實例的責任轉移到單獨的對象上,這個對象本身可能在域模型中沒有職責,但仍然是領域設計的一部分。提供一個封裝所有複雜程序集的接口,並且不要求客戶端引用實例化對象的具體類。將創建一個完整的聚合作爲一部分,強制實施它的不變性。創建一個複雜的值對象,可能是在將元素與構建器組合後。

 

 

作者:Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/DDDReference3.html

 

 

如果你想及時得到個人自寫文章的消息推送,歡迎掃描下面的二維碼~。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章