模式:AGGREGATE

目錄

 

模式:AGGREGATE

所有事務應用一組規則(重要)

採購訂單的完整性(例子)


模式:AGGREGATE

減少設計中的關聯有助於簡化對象之間的遍歷,並在某種程度上限制關係的急劇增多。但大多數業務領域中的對象都具有十分複雜的聯繫,以至於最終會形成很長、很深的對象引用路徑,我們不得不在這個路徑上追蹤對象。在某種程度上,這種混亂狀態反映了現實世界,因爲現實世界中就很少有清晰的邊界。但這卻是軟件設計中的一個重要問題。

假設我們從數據庫中刪除一個Person對象。這個人的姓名、出生日期和工作描述要一起被刪除,但要如何處理地址呢?可能還有其他人住在同一地址。如果刪除了地址,那些Person對象將會引用一個被刪除的對象。如果保留地址,那麼垃圾地址在數據庫中會累積起來。雖然自動垃圾收集機制可以清除垃圾地址,但這也只是一種技術上的修復;就算數據庫系統存在這種處理機制,一個基本的建模問題依然被忽略了。

即便是在考慮孤立的事務時,典型對象模型中的關係網也使我們難以斷定一個修改會產生哪些潛在的影響。僅僅因爲存在依賴就更新系統中的每個對象,這樣做是不現實的。在多個客戶對相同對象進行併發訪問的系統中,這個問題更加突出。當很多用戶對系統中的對象進行查詢和更新時,必須防止他們同時修改互相依賴的對象。範圍錯誤將導致嚴重的後果。

在具有複雜關聯的模型中,要想保證對象更改的一致性是很困難的。不僅互不關聯的對象需要遵守一些固定規則,而且緊密關聯的各組對象也要遵守一些固定規則。然而,過於謹慎的鎖定機制又會導致多個用戶之間毫無意義地互相干擾,從而使系統不可用。

換句話說,我們如何知道一個由其他對象組成的對象從哪裏開始,又到何處結束呢?在任何具有持久化數據存儲的系統中,對數據進行修改的事務必須要有範圍,而且要有保持數據一致性的方式(也就是說,保持數據遵守固定規則)。數據庫支持各種鎖機制,而且可以編寫一些測試來驗證。但這些特殊的解決方案分散了人們對模型的注意力,很快人們就會回到“走一步,看一步”的老路上來。

實際上,要想找到一種兼顧各種問題的解決方案,要求對領域有深刻的理解,例如,要了解特定類實例之間的更改頻率這樣的深層次因素。我們需要找到一個使對象間衝突較少而固定規則聯繫更緊密的模型。

儘管從表面上看這個問題是數據庫事務方面的一個技術難題,但它的根源卻在模型,歸根結底是由於模型中缺乏明確定義的邊界。從模型得到的解決方案將使模型更易於理解,並且使設計更易於溝通。當模型被修改時,它將引導我們對實現做出修改。

人們已經開發出很多模式(scheme)來定義模型中的所屬關係。下面這個簡單但嚴格的系統就提煉自這些概念,其包括一組用於實現事務(這些事務用來修改對象及其所有者)的規則。

首先,我們需要用一個抽象來封裝模型中的引用。AGGREGATE就是一組相關對象的集合,我們把它作爲數據修改的單元。每個AGGREGATE都有一個根(root)和一個邊界(boundary)。邊界定義了AGGREGATE的內部都有什麼。根則是AGGREGATE所包含的一個特定ENTITY。對AGGREGATE而言,外部對象只可以引用根,而邊界內部的對象之間則可以互相引用。除根以外的其他ENTITY都有本地標識,但這些標識只在AGGREGATE內部才需要加以區別,因爲外部對象除了根ENTITY之
外看不到其他對象。

所有事務應用一組規則(重要)

汽車修配廠的軟件可能會使用汽車模型。如下圖所示。汽車是一個具有全局標識的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。圖6-9展示了當George、Amanda和Sam在不同PO上工作時將會發生的情況。

工作變得越來越麻煩,因爲在Part上出現了很多爭用的情況。這樣就會發生下圖中的結果:3個人都需要等待。

現在我們可以開始改進模型,在模型中加入以下業務知識。

  1. Part在很多PO中使用(會產生高競爭)。
  2. 對Part的修改少於對PO的修改。
  3. 對Price(價格)的修改不一定要傳播到現有PO,它取決於修改價格時PO處於什麼狀態。

當考慮已經交貨並存檔的PO時,第三點尤爲明顯。它們顯示的當然是填寫時的價格,而不是當前價格。

按照上圖,這個模型得到的實現可以確保滿足PO和採購項相關的固定規則,同時,修改部件的價格將不會立即影響引用部件的採購項。涉及面更廣的規則可以通過其他方式來滿足。例如,系統可以每天爲用戶列出價格過期的採購項,這樣用戶就可以決定是更新還是去掉採購項。

但這並不是必須一直保持的固定規則。通過減少採購項對Part的依賴,可以避免爭用,並且能夠更好地反映出業務的現實情況。同時,加強PO與採購項之間的關係可以確保遵守這條重要的業務規則。

AGGREGATE強制了PO與採購項之間符合業務實際的所屬關係。PO和採購項的創建及刪除很自然地被聯繫在一起,而Part的創建和刪除卻是獨立的。

AGGREGATE劃分出一個範圍,在這個範圍內,生命週期的每個階段都必須滿足一些固定規則。接下來要討論的兩種模式FACTORY和REPOSITORY都是在AGGREGATE上執行操作,它們將特定生命週期轉換的複雜性封裝起來……

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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