[译文]Domain Driven Design Reference(三)—— 模型驱动设计的构建模块
本書是Eric Evans對他自己寫的《領域驅動設計-軟件核心復雜性應對之道》的一本字典式的參考書,可用于快速查找《領域驅動設計》中的諸多概念及其簡明解釋。
?
?
其它本系列其它文章地址:
[譯文]Domain Driven Design Reference(一)—— 前言
[譯文]Domain Driven Design Reference(二)—— 讓模型起作用
[譯文]Domain Driven Design Reference(三)—— 模型驅動設計的構建模塊
?
?
Ⅱ.模型驅動設計的構建模塊
這些模式根據(jù)領域驅動設計,廣泛地推行了面向對象設計的最佳實踐。他們指導決策來提煉模型,并使模型和實現(xiàn)保持一致,每一個都增強了其他的有效性。仔細制定模型元素的細節(jié)為開發(fā)人員提供了一個穩(wěn)定的平臺,從中可以探索模型并使其與實現(xiàn)保持緊密聯(lián)系。
?
分層架構
在面向對象的程序中,用戶界面,數(shù)據(jù)庫和其他支持代碼通常會直接寫入業(yè)務對象。額外的業(yè)務邏輯被嵌入在UI部件和數(shù)據(jù)庫腳本的行為中。發(fā)生這種情況是因為在短期內(nèi),這樣做是最簡單的方法。
當與領域相關的代碼通過如此大量的其他代碼被擴散時,變得非常難以理解和推理。UI的表面變化實際上可以改變業(yè)務邏輯。要更改業(yè)務規(guī)則,可能需要仔細跟蹤UI代碼,數(shù)據(jù)庫代碼或其他程序元素。實現(xiàn)一致的、模型驅動的對象變得不切實際。自動化測試變得難以進行。由于每個活動都涉及到所有的技術和邏輯,程序必須保持非常簡單,否則就無法理解。
因此:
隔離領域模型和業(yè)務邏輯的表達形式,并消除對基礎架構,用戶界面甚至非業(yè)務邏輯的應用程序邏輯的依賴。將一個復雜的程序分成多個層。在每個層次內(nèi)開發(fā)一個內(nèi)聚的設計,并且僅依賴于下面的層。遵循標準的建筑模式,為上面的分層提供松散的耦合。將所有與領域模型相關的代碼集中在一個層中,并將其與用戶界面,應用程序和基礎設施的代碼隔離。領域對象沒有顯示自己,存儲自己,管理應用程序任務等等的職責,可以集中在表達領域模型上。這使得一個模型能夠發(fā)展到足夠豐富,足夠清晰,能夠捕獲必要的業(yè)務知識并將其付諸實踐。
這里的關鍵目標是隔離。 諸如“六邊形架構”之類的相關模式可以起到允許我們的領域模型表現(xiàn)避免依賴和引用其他系統(tǒng)問題,甚至更好的效果。
?
實體
許多對象代表了一個連續(xù)的具有身份標識的主線,貫穿整個生命周期,盡管其屬性可能會改變。一些對象不是主要由它們的屬性定義的。它們代表了貫穿時間并經(jīng)常跨越不同展現(xiàn)形式的主線的身份標識。有時這樣的對象必須與另一個對象匹配,即使屬性不同。錯誤的身份可能導致數(shù)據(jù)損壞。
因此:
當一個對象被它的身份而不是它的屬性所區(qū)分時,把它作為它在模型中定義的要點。保持簡單的類定義,并關注生命周期的連續(xù)性和身份標識。
定義一個區(qū)分每個對象的方法,而不管它的形式或歷史。?對通過屬性調(diào)用匹配對象的需求保持警惕。定義一個保證為每個對象產(chǎn)生唯一結果的操作,可能通過附加一個保證唯一的符號。這種標識手段可能來自外部,也可能是由系統(tǒng)創(chuàng)建的任意標識符,但必須符合模型中的身份標識區(qū)別。
模型必須定義什么是同樣的事情。
(又稱參考對象)
?
值對象
有些對象描述或計算事物的一些特征。
許多對象沒有概念上的身份標識。
跟蹤實體的身份標識至關重要。但將身份標識附加到其他對象可能會傷害系統(tǒng)性能,增加分析工作,并使所有對象看起來都一模一樣。軟件設計是一個復雜的持續(xù)戰(zhàn)斗。我們必須作出區(qū)分,以便只有在必要時才進行特殊處理。
然而,如果我們把這種類型的對象看作是缺少身份的話,那么我們并沒有在我們的工具箱或詞匯中添加太多東西。實際上,這些對象具有自己的特點,對模型本身也有意義。 這些是描述事物的對象。
因此:
當您只關心模型元素的屬性和邏輯時,將其歸類為值對象。使其表達它傳達的屬性的含義并賦予它相關的功能。將值對象視為不可變的。使所有操作是不依賴任何可變狀態(tài)的無副作用函數(shù)。不要給值對象任何身份標識,并避免保留實體所必需的設計復雜性。
?
領域事件
領域專家關心的事情發(fā)生了。一個實體負責跟蹤其狀態(tài)和規(guī)定其生命周期的規(guī)則。但是,如果你需要知道狀態(tài)變化的實際原因,這通常是不明確的,并且可能很難解釋系統(tǒng)如何實現(xiàn)它。審計線索可以允許跟蹤,但通常不適合用于程序本身的邏輯。實體的變化歷史可以允許訪問先前的狀態(tài),但忽略這些變化的含義,以便對信息的任何操作都是程序性的,并且經(jīng)常被拋出領域層。
分布式系統(tǒng)中出現(xiàn)了一系列獨特但又相關的問題。分布式系統(tǒng)的狀態(tài)在任何時候都不能保持完全一致。我們始終保持聚合內(nèi)部一致,而異步的進行其他更改。當更改在網(wǎng)絡的節(jié)點間傳播時,可能很難解決無序或來自不同來源的多個更新。
因此:
將關于領域中活動的模型信息視為一系列離散事件。將每個事件表示為一個領域對象。這些不同于系統(tǒng)事件,它們反映了軟件本身的活動,雖然通常系統(tǒng)事件與領域事件相關聯(lián)或者作為領域事件的響應的一部分,或者作為將領域事件的信息攜帶到系統(tǒng)中的一種方式。
領域事件是領域模型的一個完整的部分,是領域中發(fā)生的事情的表示形式。忽略不相關的領域活動,同時明確領域專家想要跟蹤或者被通知的事件,或者與其他模型對象中的狀態(tài)改變相關聯(lián)的事件。
在分布式系統(tǒng)中,實體的狀態(tài)可以從特定節(jié)點的當前已知的領域事件中推斷出來,從而在沒有關于整個系統(tǒng)的完整信息的情況下得到相關的模型。
領域事件通常是不可變的,因為它們是過去的某種事物的記錄。除了對事件的描述之外,領域事件通常包含事件發(fā)生時間的時間戳以及事件涉及的實體的身份標識。此外,領域事件通常具有單獨的時間戳,指示事件何時進入系統(tǒng)以及使其進入系統(tǒng)的人的身份標識。如果有用,領域事件的身份標識可以基于這些屬性的一些集合。所以,例如,如果同一個事件的兩個實例到達一個節(jié)點,則它們可以被識別為相同的。
?
服務
有時候,這不是一回事。領域的一些概念由模型作為對象是不自然的。強制所需的領域功能成為實體或者值對象的職責,要么篡改基于模型的對象的定義,要么添加無意義的虛擬對象。
因此:
當領域中的重要流程或轉換不是實體或值對象的自然職責時,添加一個操作到模型中作為一個單獨的接口同時聲明為一個服務。定義一個服務契約,一組關于與服務交互的聲明。用一個特定限界上下文的通用語言來陳述這些聲明。給服務一個名字,這也成為通用語言的一部分。
?
模塊
每個人都使用模塊,但很少將它們視為模型的完整部分。代碼被分解成各種類別,從技術架構的各個方面到開發(fā)人員的工作任務。即使是做了很多重構的開發(fā)人員也傾向于使用項目早期構思的模塊。
耦合和凝聚力的解釋傾向于使它們聽起來像是技術指標,根據(jù)關聯(lián)和相互作用的分布進行機械的判斷。然而,這不僅僅是將代碼劃分為模塊,還包括概念。一個人一次可以思考多少事情是有限的(因此耦合度低),不連貫的想法片段很難被理解為一個無差別的想法(因此具有很高的內(nèi)聚性)。
因此:
選擇能夠講述系統(tǒng)故事的模塊,并包含一系列內(nèi)聚的概念。讓模塊名稱成為通用語言的一部分。模塊是模型的一部分,它們的名稱應反映對領域的洞察。
這通常會導致模塊之間的低耦合,但是如果它不尋找一種方法來改變模型來分解概念,或者是一個被忽視的概念,它可能是一個能夠以有意義的方式將元素組合在一起的模塊的基礎。在可以被獨立地理解和推理的概念上尋求低耦合。根據(jù)高層領域概念對模型進行細化直到它被劃分,并將相應的代碼解耦。
?
聚合
要保證復雜關聯(lián)模型中對象變化的一致性是很困難的。他們能夠被是概念上的構成部分的其它對象的變化所掩蓋。在多個服務器之間分發(fā)對象或設計異步事務時會出現(xiàn)類似的問題。
因此:
將實體和值對象集中到聚合中并在周圍定義邊界。選擇一個實體作為每個聚合的根,并允許外部對象僅保留對根的引用(對內(nèi)部成員的引用僅在一個操作中返回出去才能使用)。定義聚合的屬性和不變量作為一個整體,并將這個約束的責任賦予根【這里指的是聚合根】或某種指定的框架機制。
使用相同的聚合邊界來管理事務和分配。
在一個聚合邊界內(nèi),同步地應用一致性規(guī)則。 跨越邊界,異步地處理更新。
在一臺服務器上共同維護一個聚合。允許不同的聚合在節(jié)點間分配。
如果這些設計決策沒有受到聚合邊界的良好指導,請重新考慮模型。是領域的場景正在暗示著一個重要的新見解嗎?這種改變通常會提高模型的表現(xiàn)力和靈活性,并解決事務和分配問題。
?
倉儲
查詢通用語言表達的聚合。
可遍歷的關聯(lián)的擴散只用于找到弄亂模型的東西。在成熟模型中,查詢經(jīng)常表達領域概念。然而查詢可能會導致問題。
應用大多數(shù)數(shù)據(jù)庫訪問基礎架構的純粹技術復雜性迅速吞噬了客戶端代碼,導致開發(fā)人員陷入了領域層,使得模型無關緊要。
查詢框架可能會封裝大部分的技術復雜性,使開發(fā)人員能夠以更自動化或聲明的方式從數(shù)據(jù)庫中提取所需的確切數(shù)據(jù),但這只能解決一部分問題。
不受約束的查詢可能會從對象中拉出特定的字段,違反封裝,或從聚合內(nèi)部實例化幾個特定的對象,讓聚合根變得充滿變數(shù)并使這些對象無法執(zhí)行領域模型的規(guī)則。領域邏輯移入查詢和應用程序層代碼,實體和值對象變成僅僅為數(shù)據(jù)容器。
因此:
對于需要全局訪問的每種聚合類型,創(chuàng)建一個服務,它可以提供所有聚合根類型的對象的在一個內(nèi)存集合中的錯覺。通過一個大家都知道的全局接口設置訪問。提供添加和刪除對象的方法,這將封裝實際數(shù)據(jù)往數(shù)據(jù)存儲中的插入或刪除。提供基于對領域專家有意義的標準來選擇對象的方法。返回完全實例化的對象或屬性值符合條件的對象集合,從而封裝實際的存儲和查詢技術,或者返回給予以惰性的方式完全實例化的聚合的幻覺的代理。僅為實際需要直接訪問的聚合根提供倉儲。保持應用程序邏輯專注于模型,委托所有的對象存儲和訪問給倉儲。
?
工廠
當創(chuàng)建一個完整的,內(nèi)部一致的聚合或者一個大值對象變得復雜或者顯示太多的內(nèi)部結構時,工廠提供封裝。一個對象的創(chuàng)建本身可以是一個主要的操作,但是復雜的組裝操作不適合由創(chuàng)建的對象來承擔。將這些職責結合起來可能會產(chǎn)生難以理解并且難看的設計。讓客戶端直接組裝會混亂客戶端的設計,破壞組裝對象或集合的封裝,并且過度地將客戶端耦合到所創(chuàng)建對象的實現(xiàn)中【舉個例子,這里的客戶端可以理解成應用層或者UI層】。
因此:
將創(chuàng)建復雜對象和聚合實例的責任轉移到單獨的對象上,這個對象本身可能在域模型中沒有職責,但仍然是領域設計的一部分。提供一個封裝所有復雜程序集的接口,并且不要求客戶端引用實例化對象的具體類。將創(chuàng)建一個完整的聚合作為一部分,強制實施它的不變性。創(chuàng)建一個復雜的值對象,可能是在將元素與構建器組合后。
?
?
作者:Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/DDDReference3.html
?
?
?關于作者:張帆(Zachary,個人微信號:Zachary-ZF)。堅持用心打磨每一篇高質(zhì)量原創(chuàng)。歡迎掃描右側的二維碼~。
定期發(fā)表原創(chuàng)內(nèi)容:架構設計丨分布式系統(tǒng)丨產(chǎn)品丨運營丨一些思考。
?
如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注我的公眾號「跨界架構師」,回復「技術」,送你一份我長期收集和整理的思維導圖。
如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關注我的公眾號「跨界架構師」,回復「運營」,送你一份我長期收集和整理的思維導圖。
總結
以上是生活随笔為你收集整理的[译文]Domain Driven Design Reference(三)—— 模型驱动设计的构建模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 部署yum仓库自动挂载服务
- 下一篇: 基于工程经验的『RESTful接口设计规