領域模型與工廠樣式

此系列進一步討論「領域驅動設計」理論,作為商業級 ASP.NET MVC 樣式與架構實務一書的延伸學習教學,搭配圖書學習效果最佳 :)

物件的建立過程,可能會經過包含特定條件的檢核、或是如關聯規則的設定等等邏輯運算過程,而這些建立實體物件的程序,會讓處理主要流程的流程變得混淆,為了避免這種情形,我們可以藉由工廠樣式(Factory Pattern)隔離建立物件過程所需的程式碼,以單純化程式流程。利用工廠樣式可以有效的隔離物件複雜的建立過程,避免其中的程式碼進入商業邏輯的主要執行流程,降低應用程式的複雜度,當物件的建立邏輯需要調整時,只需變更工廠物件的內容即可。

「領域驅動設計」以聚合單元(Aggregate)組織模型物件,應用程式必須透過根實體存取聚合邊界內的其它模型物件,由於聚合單元包含一連串緊密關聯的物件,而根實體的建立,因此也會牽涉其它物件的建立,甚至包含特定的領域知識邏輯,此時的根物件通常會發展出一定的複雜度,在這種情形下,很難直接透過單一建構式來建立物件,我們需要為物件的建立過程,定義專屬的程序,讓外部程序直接引用以完成聚合根物件的建立工作,為了達到這個目的,我們需要導入工廠樣式。

考慮以下的 Order 聚合單元,其中包含 Order、OrderLine 以及 AddressInfo。



當應用程式欲處理任何有關訂單的資訊時,必須從訂單主檔-Order 實體進入,無法接觸其它的實體或是實值物件,相關物件的領域商業邏輯,都必須封裝在聚合單元中,在這種情形下,建立訂單實體物件時,意謂著需同時處理 OrderLine 以及 AddressInfo 等物件,這會是一個複雜的過程,而真實世界的開發中,一個聚合單元包含的物件則更為複雜。

物件的建立牽涉領域知識,在這個例子中,單一訂單主檔同時包含一筆以上的訂單明細,因此一筆新的訂單要成立,除了根物件Order,同時必須建立關聯的明細物件集合OrderLine,以及地址資訊 AddressInfo 物件,而這是一個實值物件,無論 OrderLine 或是 AddressInfo,最後被儲存至 Order 對應的屬性成員中,完成這個過程,才能建立一個完整的訂單聚合單元。

工廠方法透過類別方法定義,隱藏建立聚合單元的過程中,產生任何物件所需的邏輯知識,例如 OrderLine 的建立,我們可以嘗試將方法定義在 Order 類別,由類別方法負責處理,如此一來能完全從根物件建立聚合單元中的所有物件,當然包含訂單配送資訊的 AddressInfo 實值物件。



客戶端應用的外部程序,透過調用 OrderFactory 工廠方法,建立完整的聚合單元。

對於更複雜的實際開發,可以進一步徹底的切割工廠方法至獨立物件,分離物件建立的方法至獨立物件,提供開放介面給客戶端應用,避免直接建立根物件,這樣作的好處,除了可以更明確的抽離工廠方法,還可以將跨物件的方法邏輯從聚合單元獨立出來。

很多情形下,領域知識邏輯必須橫跨不同的物件,甚至聚合單元,例如建立訂單明細的過程中,同時必須考慮商品的庫存量是否足夠應付此次的訂單數量,而管理商品庫存量的商業邏輯,同樣會由其它如採購或是庫存盤點功的聚合單元共用,將這些運算邏輯封裝於獨立的物件方法,有利於其它的聚合單元共用。

考慮分層

不經由工廠方法而直接由客戶端應用執行物件的建立,在物件建立的過程中,一旦牽涉處理領域問題的商業邏輯,很容易破壞分層封裝原則,例如在應用層直接建立 Order 實體物件,由於沒有封裝庫存管理的邏輯程序,因此必須在應用層直接處理領域邏輯。



如前述說明,處理訂單明細會牽涉「庫存管理商業邏輯」,而這屬於領域層的一部份,應用層為了成功建立 OrderLine 物件,必須在應用層處理相關的邏輯,因此破壞了分層設計原則,分離工廠物件並配置於領域層,藉由公開介面,提供客戶端應用進行存取是比較好的作法。



重新設計如上圖,現在客戶端應用只需要針對工廠介面進行引用,而複雜的商業邏輯則隱藏於領域層的工廠物件當中,維持領域層的封裝原則。

經由上述的設計,此時工廠方法與物件的建立有著相當緊密的關聯,因此必須特別注意,當根物件完成建立,所有其它聚合單元中的物件,同樣需完成配置,後續當物件發生改變,工廠方法必須一併作調整。

工廠樣式的導入,可以協助應用程式適當的隱藏複雜的物件建立程序,並且避免破壞分層原則,特別是眾多實體與實值物件組合成的聚合單元,不過並非所有物件的建立,都必須設計工廠樣式提供所需的支援,當物件的建立非常單純,或是不包含其它關聯物件的時候,可以選擇直接透過建構式來完成物件的建立作業。



沒有留言: