[toc] 領域驅動設計 領域對象的生命周期 每個對象都有生命周期,如圖6 1所示。對象自創建後,可能會經歷各種不同的狀態,直至最終消亡——要麼存檔,要麼刪除。當然,很多對象是簡單的臨時對象,僅通過調用構造函數來創建,用來做一些計算,而後由垃圾收集器回收。這類對象沒必要搞得那麼複雜。但有些對象具有更 ...
目錄
領域驅動設計-領域對象的生命周期
每個對象都有生命周期,如圖6-1所示。對象自創建後,可能會經歷各種不同的狀態,直至最終消亡——要麼存檔,要麼刪除。當然,很多對象是簡單的臨時對象,僅通過調用構造函數來創建,用來做一些計算,而後由垃圾收集器回收。這類對象沒必要搞得那麼複雜。但有些對象具有更長的生命周期,其中一部分時間不是在活動記憶體中度過的。它們與其他對象具有複雜的相互依賴性。它們會經歷一些狀態變化,在變化時要遵守一些固定規則。管理這些對象時面臨諸多挑戰,稍有不慎就會偏離MODEL-DRIVEN DESIGN的軌道。
主要的挑戰有以下兩類。
(1) 在整個生命周期中維護完整性。
(2) 防止模型陷入管理生命周期複雜性造成的困境當中。
AGGREGATE(聚合),它通過定義清晰的所屬關係和邊界,並避免混亂、錯綜複雜的對象關係網來實現模型的內聚。聚合模式對於維護生命周期各個階段的完整性具有至關重要的作用。我們將註意力轉移到生命周期的開始階段,使用FACTORY(工廠)來創建和重建複雜對象和AGGREGATE(聚合),從而封裝它們的內部結構。最後,在生命周期的中間和末尾使用REPOSITORY(存儲庫)來提供查找和檢索持久化對象並封裝龐大基礎設施的手段。
儘管REPOSITORY和FACTORY本身並不是來源於領域,但它們在領域設計中扮演著重要的角色。這些結構提供了易於掌握的模型對象處理方式,使MODEL-DRIVEN DESIGN更完備。
使用AGGREGATE進行建模,並且在設計中結合使用FACTORY和REPOSITORY,這樣我們就能夠在模型對象的整個生命周期中,以有意義的單元、系統地操縱它們。AGGREGATE可以劃分出一個範圍,這個範圍內的模型元素在生命周期各個階段都應該維護其固定規則。FACTORY和REPOSITORY在AGGREGATE基礎上進行操作,將特定生命周期轉換的複雜性封裝起來。
AGGREGATE
減少設計中的關聯有助於簡化對象之間的遍歷,併在某種程度上限制關係的急劇增多。但大多數業務領域中的對象都具有十分複雜的聯繫,以至於最終會形成很長、很深的對象引用路徑,我們不得不在這個路徑上追蹤對象。在某種程度上,這種混亂狀態反映了現實世界,因為現實世界中就很少有清晰的邊界。但這卻是軟體設計中的一個重要問題。
假設我們從資料庫中刪除一個Person對象。這個人的姓名、出生日期和工作描述要一起被刪除,但要如何處理地址呢?可能還有其他人住在同一地址。如果刪除了地址,那些Person對象將會引用一個被刪除的對象。如果保留地址,那麼垃圾地址在資料庫中會累積起來。雖然自動垃圾收集機制可以清除垃圾地址,但這也只是一種技術上的修複;就算資料庫系統存在這種處理機制,一個基本的建模問題依然被忽略了。
即便是在考慮孤立的事務時,典型對象模型中的關係網也使我們難以斷定一個修改會產生哪些潛在的影響。僅僅因為存在依賴就更新系統中的每個對象,這樣做是不現實的。
在多個客戶對相同對象進行併發訪問的系統中,這個問題更加突出。當很多用戶對系統中的對象進行查詢和更新時,必須防止他們同時修改互相依賴的對象。範圍錯誤將導致嚴重的後果。
在具有複雜關聯的模型中,要想保證對象更改的一致性是很困難的。不僅互不關聯的對象需要遵守一些固定規則,而且緊密關聯的各組對象也要遵守一些固定規則。然而,過於謹慎的鎖定機制又會導致多個用戶之間毫無意義地互相干擾,從而使系統不可用。
換句話說,我們如何知道一個由其他對象組成的對象從哪裡開始,又到何處結束呢?在任何具有持久化數據存儲的系統中,對數據進行修改的事務必須要有範圍,而且要有保持數據一致性的方式(也就是說,保持數據遵守固定規則)。資料庫支持各種鎖機制,而且可以編寫一些測試來驗證。但這些特殊的解決方案分散了人們對模型的註意力,很快人們就會回到“走一步,看一步”的老路上來。
實際上,要想找到一種兼顧各種問題的解決方案,要求對領域有深刻的理解,例如,要瞭解特定類實例之間的更改頻率這樣的深層次因素。我們需要找到一個使對象間衝突較少而固定規則聯繫更緊密的模型。
儘管從錶面上看這個問題是資料庫事務方面的一個技術難題,但它的根源卻在模型,歸根結底是由於模型中缺乏明確定義的邊界。從模型得到的解決方案將使模型更易於理解,並且使設計更易於溝通。當模型被修改時,它將引導我們對實現做出修改。
首先,我們需要用一個抽象來封裝模型中的引用。AGGREGATE就是一組相關對象的集合,我們把它作為數據修改的單元。每個AGGREGATE都有一個根(root)和一個邊界(boundary)。邊界定義了AGGREGATE的內部都有什麼。根則是AGGREGATE所包含的一個特定ENTITY。對AGGREGATE而言,外部對象只可以引用根,而邊界內部的對象之間則可以互相引用。除根以外的其他ENTITY都有本地標識,但這些標識只在AGGREGATE內部才需要加以區別,因為外部對象除了根ENTITY之外看不到其他對象。
汽車修配廠的軟體可能會使用汽車模型。如圖6-2所示。汽車是一個具有全局標識的ENTITY:我們需要將這部汽車與世界上所有其他汽車區分開(即使是一些非常相似的汽車)。我們可以使用車輛識別號來進行區分,車輛識別號是為每輛新汽車分配的唯一標識符。我們可能想通過4個輪子的位臵跟蹤輪胎的轉動歷史。我們可能想知道每個輪胎的里程數和磨損度。要想知道哪個輪胎在哪兒,必須將輪胎標識為ENTITY。當脫離這輛車的上下文後,我們很可能就不再關心這些輪胎的標識了。如果更換了輪胎並將舊輪胎送到回收廠,那麼軟體將不再需要跟蹤它們,它們會成為一堆廢舊輪胎中的一部分。沒有人會關心它們的轉動歷史。更重要的是,即使輪胎被安在汽車上,也不會有人通過系統查詢特定的輪胎,然後看看這個輪胎在哪輛汽車上。人們只會在資料庫中查找汽車,然後臨時查看一下這部汽車的輪胎情況。因此,汽車是AGGREGATE的根ENTITY,而輪胎處於這個AGGREGATE的邊界之內。另一方面,發動機組上面都刻有序列號,而且有時是獨立於汽車被跟蹤的。在一些應用程式中,發動機可以是自己的AGGREGATE的根。
固定規則(invariant)是指在數據變化時必須保持的一致性規則,其涉及AGGREGATE成員之間的內部關係。而任何跨越AGGREGATE的規則將不要求每時每刻都保持最新狀態。通過事件處理、批處理或其他更新機制,這些依賴會在一定的時間內得以解決。但在每個事務完成時,AGGREGATE內部所應用的固定規則必須得到滿足,如圖6-3所示。
現在,為了實現這個概念上的AGGREGATE,需要對所有事務應用一組規則。
根ENTITY具有全局標識,它最終負責檢查固定規則。
根ENTITY具有全局標識。邊界內的ENTITY具有本地標識,這些標識只在AGGREGATE內部才是唯一的。
AGGREGATE外部的對象不能引用除根ENTITY之外的任何內部對象。根ENTITY可以把對內部ENTITY的引用傳遞給它們,但這些對象只能臨時使用這些引用,而不能保持引用。根可以把一個VALUE OBJECT的副本傳遞給另一個對象,而不必關心它發生什麼變化,因為它只是一個VALUE,不再與AGGREGATE有任何關聯。
作為上一條規則的推論,只有AGGREGATE的根才能直接通過資料庫查詢獲取。所有其他對象必須通過遍歷關聯來發現。
AGGREGATE內部的對象可以保持對其他AGGREGATE根的引用。
刪除操作必須一次刪除AGGREGATE邊界之內的所有對象。(利用垃圾收集機制,這很容易做到。由於除根以外的其他對象都沒有外部引用,因此刪除了根以後,其他對象均會被回收。)
當提交對AGGREGATE邊界內部的任何對象的修改時,整個AGGREGATE的所有固定規則都必須被滿足。
我們應該將ENTITY和VALUE OBJECT分門別類地聚集到AGGREGATE中,並定義每個AGGREGATE的邊界。在每個AGGREGATE中,選擇一個ENTITY作為根,並通過根來控制對邊界內其他對象的所有訪問。只允許外部對象保持對根的引用。對內部成員的臨時引用可以被傳遞出去,但僅在一次操作中有效。由於根控制訪問,因此不能繞過它來修改內部對象。這種設計有利於確保AGGREGATE中的對象滿足所有固定規則,也可以確保在任何狀態變化時AGGREGATE作為一個整體滿足固定規則。
有一個能夠聲明AGGREGATE的技術框架是很有幫助的,這樣就可以自動實施鎖機制和其他一些功能。如果沒有這樣的技術框架,團隊就必須靠自我約束來使用事先商定的AGGREGATE,並按照這些AGGREGATE來編寫代碼。
示例 採購訂單的完整性
一個典型的採購訂單(Purchase Order,PO)視圖,它被分解為採購項(Line Item),一條固定規則是採購項的總量不能超過PO總額的限制。當前實現存在以下3個互相關聯的問題。
(1) 固定規則的實施。當添加新採購項時,PO檢查總額,如果新增的採購項使總額超出限制,則將PO標記為無效。正如我們將要看到的那樣,這種保護機制並不充分。
(2) 變更管理。當PO被刪除或存檔時,各個採購項也將被一塊處理,但模型並沒有給出關係應該在何處停止。在不同時間更改部件(Part)價格所產生的影響也不明確。
(3) 資料庫共用。資料庫會出現由於多個用戶競爭使用而帶來的問題。
多個用戶將併發地輸入和更新各個PO,因此必須防止他們互相干擾。讓我們從一個非常簡單的策略開始,當一個用戶開始編輯任何一個對象時,鎖定該對象,直到用戶提交事務。這樣,當George編輯採購項001時,Amanda就無法訪問該項。Amanda可以編輯其他PO上的任何採購項(包括George正在編輯的PO上的其他採購項)
每個用戶都將從資料庫讀取對象,併在自己的記憶體空間中實例化對象,而後在那裡查看和編輯對象。只有當開始編輯時,才會請求進行資料庫鎖定。因此,George和Amanda可以同時工作,只要他們不同時編輯相同的採購項即可。一切正常,直到George和Amanda開始編輯同一個PO上的不同採購項.
從這兩個用戶和他們各自軟體的角度來看,他們的操作都沒有問題,因為他們忽略了事務期間資料庫其他部分所發生的變化,而且每個用戶都沒有修改被對方鎖定的採購項。當這兩個用戶保存了修改之後,資料庫中就存儲了一個違反領域模型固定規則的PO。一條重要的業務規則被破壞了,但並沒有人知道
顯然,鎖定單個行並不是一種充分的保護機制。如果一次鎖定一個PO,可以防止這樣的問題發生
直到Amanda解決這個問題之前,程式將不允許保存這個事務,Amanda可以通過提高限額或減少一把吉他來解決此問題。這種機制防止了問題,如果大部分工作分佈在多個PO上,那麼這可能是個不錯的解決方案。但如果是很多人同時對一個大PO的不同項進行操作時,這種鎖定機制就顯得很笨拙了。
即便是很多小PO,也存在其他方法破壞這條固定規則。讓我們看看“Part”。如果在Amanda將長號加入訂單時,有人更改了長號的價格,這不也會破壞固定規則嗎?
我們試著除了鎖定整個PO之外,也鎖定Part,當George、Amanda和Sam在不同PO上工作時將會發生的情況。
工作變得越來越麻煩,因為在Part上出現了很多爭用的情況。這樣就會發生圖6-10中的結果:3個人都需要等待。
現在我們可以開始改進模型,在模型中加入以下業務知識。
(1) Part在很多PO中使用(會產生高競爭)。
(2) 對Part的修改少於對PO的修改。
(3) 對Price(價格)的修改不一定要傳播到現有PO,它取決於修改價格時PO處於什麼狀態。
當考慮已經交貨並存檔的PO時,第三點尤為明顯。它們顯示的當然是填寫時的價格,而不是當前價格。
這個模型得到的實現可以確保滿足PO和採購項相關的固定規則,同時,修改部件的價格將不會立即影響引用部件的採購項。涉及面更廣的規則可以通過其他方式來滿足。例如,系統可以每天為用戶列出價格過期的採購項,這樣用戶就可以決定是更新還是去掉採購項。但這並不是必須一直保持的固定規則。通過減少採購項對Part的依賴,可以避免爭用,並且能夠更好地反映出業務的現實情況。同時,加強PO與採購項之間的關係可以確保遵守這條重要的業務規則。
AGGREGATE強制了PO與採購項之間符合業務實際的所屬關係。PO和採購項的創建及刪除很自然地被聯繫在一起,而Part的創建和刪除卻是獨立的。
AGGREGATE劃分出一個範圍,在這個範圍內,生命周期的每個階段都必須滿足一些固定規則。接下來要討論的兩種模式FACTORY和REPOSITORY都是在AGGREGATE上執行操作,它們將特定生命周期轉換的複雜性封裝起來……
個人理解:完全沒看懂,好像是建模處理資源競爭的問題。
FACTORY
當創建一個對象或創建整個AGGREGATE時,如果創建工作很複雜,或者暴露了過多的內部結構,則可以使用FACTORY進行封裝。
對象的功能主要體現在其複雜的內部配臵以及關聯方面。我們應該一直對對象進行提煉,直到所有與其意義或在交互中的角色無關的內容被完全剔除為止。一個對象在它的生命周期中要承擔大量職責。如果再讓複雜對象負責自身的創建,那麼職責過載將會導致問題。
汽車發動機是一種複雜的機械裝臵,它由數十個零件共同協作來履行發動機的職責——使軸轉動。我們可以試著設計一種發動機組,讓它自己抓取一組活塞並塞到汽缸中,火花塞也可以自己找到插孔並把自己擰進去。但這樣組裝的複雜機器可能沒有我們常見的發動機那樣可靠或高效。相反,我們用其他東西來裝配發動機。或許是機械師,或者是工業機器人。無論是機器人還是人,實際上都比二者要裝配的發動機複雜。裝配零件的工作與使軸旋轉的工作完全無關。只是在生產汽車時才需要裝配工,我們駕駛時並不需要機器人或機械師。由於汽車的裝配和駕駛永遠不會同時發生,因此將這兩種功能合併到同一個機制中是毫無價值的。同理,裝配複雜的複合對象的工作也最好與對象要執行的工作分開。
正如對象的介面應該封裝對象的實現一樣(從而使客戶無需知道對象的工作機理就可以使用對象的功能),FACTORY封裝了創建複雜對象或AGGREGATE所需的知識。它提供了反映客戶目標的介面,以及被創建對象的抽象視圖。
應該將創建複雜對象的實例和AGGREGATE的職責轉移給單獨的對象,這個對象本身可能沒有承擔領域模型中的職責,但它仍是領域設計的一部分。提供一個封裝所有複雜裝配操作的介面,而且這個介面不需要客戶引用要被實例化的對象的具體類。在創建AGGREGATE時要把它作為一個整體,並確保它滿足固定規則。
FACTORY有很多種設計方式。[Gamma et al.1995]中詳盡論述了幾種特定目的的創建模式,包括FACTORY METHOD(工廠方法)、ABSTRACT FACTORY(抽象工廠)和BUILDER(構建器)。該書主要研究了適用於最複雜的對象構造問題的模式。本書的重點並不是深入討論FACTORY的設計問題,而是要表明FACTORY的重要地位——它是領域設計的重要組件。正確使用FACTORY有助於保證MODEL-DRIVEN DESIGN沿正確的軌道前進。
任何好的工廠都需滿足以下兩個基本需求
(1) 每個創建方法都是原子的,而且要保證被創建對象或AGGREGATE的所有固定規則。FACTORY生成的對象要處於一致的狀態。在生成ENTITY時,這意味著創建滿足所有固定規則的整個AGGREGATE,但在創建完成後可以向聚合添加可選元素。在創建不變的VALUE OBJECT時,這意味著所有屬性必須被初始化為正確的最終狀態。如果FACTORY通過其介面收到了一個創建對象的請求,而它又無法正確地創建出這個對象,那麼它應該拋出一個異常,或者採用其他機制,以確保不會返回錯誤的值。
(2) FACTORY應該被抽象為所需的類型,而不是所要創建的具體類。[Gamma et al.1995]中的高級FACTORY模式介紹了這一話題
個人理解:這個我懂了,對象的創建如果非常複雜(要組裝或者初始化很多參數,規則一定)就可以使用工廠模式,技術開發人員應該非常瞭解工廠模式,業務不需要知道啥是工廠模式。
總結:沒看懂,這部分更加註重程式對象和資料庫的設計,還有就是模型的生命周期,好好瞭解下設計模式和資料庫設計範式應該就夠了。