EntityFramework之領域驅動設計實踐(六)

模型對象的生命週期 - 工廠

首先應該認識到,是對象就有生命週期。這一點無論在面嚮對象語言還是在領域驅動設計中都適用。在領域驅動設計中,模型對象生命週期可以簡要地用下圖表示:

28169563317

通過上圖可以看到,對象通過工廠從無到有創建,創建後處於活動狀態,此時可以參與領域層的業務處理;對象通過倉儲實現持久化(也就是我們常說的“保存”)和重建(也就是我們常說的“讀取”)。內存中的對象通過析構而消亡,處於持久化狀態的對象則通過倉儲進行撤銷(也就是我們常說的“刪除”)。整個狀態轉換過程非常清晰。

現在引出了管理模型對象生命週期的兩種角色:工廠和倉儲。同時也需要注意的是,工廠和倉儲的操作都是基於聚合根(Aggregate Root)的,而不僅僅是針對實體的。關於倉儲,內容會比較多,我在下一節單獨講述。在本節介紹一下工廠在.NET實體框架(EntityFramework)中的實現。

在打開了.NET實體框架自動生成的Entity Data Model Source Code文件後,我們發現,.NET實體框架爲每一個實體添加了一個工廠方法,該方法包含了一系列原始數據類型和值類型的參數。比如,我們案例中的 Customer實體就有如下的代碼:

隱藏行號 複製代碼 Customer Factory
  1. /// <summary>
    
  2. /// Create a new Customer object.
    
  3. /// </summary>
    
  4. /// <param name="id">Initial value of the Id property.</param>
    
  5. /// <param name="name">Initial value of the Name property.</param>
    
  6. /// <param name="billingAddress">Initial value of the BillingAddress property.</param>
    
  7. /// <param name="deliveryAddress">Initial value of the DeliveryAddress property.</param>
    
  8. /// <param name="loginName">Initial value of the LoginName property.</param>
    
  9. /// <param name="loginPassword">Initial value of the LoginPassword property.</param>
    
  10. /// <param name="dayOfBirth">Initial value of the DayOfBirth property.</param>
    
  11. public static Customer CreateCustomer(global::System.Int32 id, Name name, Address billingAddress, 
  12.                                              Address deliveryAddress, global::System.String loginName, 
  13.                                              global::System.String loginPassword, global::System.DateTime dayOfBirth)
    
  14. {
    
  15.     Customer customer = new Customer();
    
  16.     customer.Id = id;
    
  17.     customer.Name = StructuralObject.VerifyComplexObjectIsNotNull(name, "Name");
    
  18.     customer.BillingAddress = StructuralObject.VerifyComplexObjectIsNotNull(billingAddress, "BillingAddress");
    
  19.     customer.DeliveryAddress = StructuralObject.VerifyComplexObjectIsNotNull(deliveryAddress, "DeliveryAddress");
    
  20.     customer.LoginName = loginName;
    
  21.     customer.LoginPassword = loginPassword;
    
  22.     customer.DayOfBirth = dayOfBirth;
    
  23.     return customer;
    
  24. }
    
  25. 
    

 

那麼在創建一個Customer實體的時候,就可以使用 Customer.CreateCustomer工廠方法。看來.NET實體框架已經離領域驅動設計的思想比較接近了,下面有幾點需要說明:

 

  • 使用該工廠方法創建Customer實體時,需要給第一個參數 “global::System.Int32 id”賦值,而實際上這個ID值是用在持久化機制上的,在實體對象被創建的時候,這個ID值不應該由開發人員指定。因此,在這裏要開發人員強行指定一個 id值就顯得多餘。事實上,.NET實體框架中的每個實體都是繼承於EntityObject類,而該類中有個EntityKey的屬性,是被用作實體的 Key的,因此我們這裏的ID值肯定是由持久化機制進行維護的。從這裏也可以看出,領域驅動設計中的實體會有兩個標識符:一個是基於業務架構的,另一個是基於技術架構的。拿銷售訂單打比方,我們從界面上看到的更多是類似“SO0029473858” 這樣的標識符,而不是一個整數或者GUID
  • 該工廠方法能夠創建一個Customer實體,爲實體的各個成員屬性賦值,並連帶創建與該實體相關的值對象,聚合成員(比如 Customer的CreditCards)是在使用的時候進行創建並填充的,這樣做既符合“對象創建應該基於聚合”的思想,又能提高系統性能。比如,下面的單體測試用來檢測使用工廠創建的Customer對象,其CreditCards屬性是否爲null(如果爲null,則證明聚合根並沒有合理地維護聚合的完整性):
    301440134452
  • .NET實體框架僅僅爲每個實體提供了一個最爲簡單的工廠方法。“工廠”的概念,在領域驅動設計中具有如下的最佳實踐:
    • 工廠可以隱藏對象創建的細節,因爲對象的創建不屬於業務領域需要考慮的問題
    • 工廠用來創建整個聚合,從而維護聚合所代表的領域含義
    • 可以在聚合根中添加工廠方法,也可以使用工廠類。也就是說,可以創建一個CustomerFactory的類,在其中定義 CreateCustomer方法。具體是選用工廠方法還是工廠類,應該根據需求而定
    • 當需要對被創建的實體傳入參數時,應該儘可能地減小耦合性,比如可以使用抽象類或者接口作爲參數類型

到這裏你會發現,工廠和倉儲好像有這一種聯繫,即它們都能夠創建對象,而區別在於,工廠是對象從無到有的創建,倉儲則更偏向於“重建”。倉儲要比工廠更爲複雜,因爲倉儲需要跟持久化機制這一技術架構打交道。在接下來的文章中,我會介紹一種基於.NET實體框架,但又不被實體框架制約的倉儲的實現方式。

 

 

-----【以下爲原文網友評論及回覆信息】-----
 

Re:實體框架之領域驅動實踐(六)

[ 2009-12-31 11:48:00 | By: xiaos(遊客) ]

感覺repository 職責應該是關注get和load,而factory則是create。

以下爲blog主人的回覆:
不錯,文中我也提到了:“工廠是對象從無到有的創建,倉儲則更偏向於“重建””。Repository需要與技術架構打交道,而Factory則不需要。

Re:實體框架之領域驅動實踐(六)

[ 2010-2-9 21:34:00 | By: haojie77 ]

我最近也在學習DDD, 你這篇文章中提到的"從這裏也可以看出,領域驅動設計中的實體會有兩個標識符:一個是基於業務架構的,另一個是基於技術架構的。" 我有兩個疑問.
1. 我的感覺在domain中,Order的標識符就應該是諸如“SO0029473858”的字符串. 而GUID只是爲了在數據庫存儲而用到的(或者是EF必須要用到), 是否所謂的GUID根本不能算是領域概念中實體的標識符? 之前看到別人的帖子說切忌不要把標識符當成數據庫主鍵, 一個entity是否可以有n個(n>=2)標識符?
2. 標識符和數據庫主鍵應該不是一一對應的關係,是嗎? 標識符往往映射到數據庫主鍵, 但標識符不一定就是數據庫主鍵. 我這樣的理解對嗎? 
我剛開始學習DDD, 所以對一些概念不是很清晰,希望能從您這裏學習到更多關於DDD的東西,很期待你之後的(八,九,十...),謝謝!

以下爲blog主人的回覆:
是的,一定要將數據庫主鍵與實體鍵區分開來。原因很簡單,前者是技術架構的內容,而後者則是業務領域。在領域模型中,具有相同實體鍵的兩個對象可以認定爲同一個實體。當然,完全可以把業務實體鍵用作數據庫主鍵,比如:數據庫裏完全可以用“SO0029473858”這樣的銷售訂單號作爲 SalesTable的主鍵,但請注意了,如果是這樣的話,你必須在技術的角度生成這個鍵值,然後在持久化實體之前將其賦給實體鍵。換句話說,當使用實體鍵用作數據庫的主鍵時,維護這個鍵值的機制是你的系統本身,而不是數據庫。這可能會對性能造成一定的影響,《領域驅動設計》一書中也提到過這個問題。相關的經典案例就是Microsoft Dynamics AX中的Number Sequence機制,它是一個可以深度擴展的序列號生成機制,在Dynamics AX中,諸如銷售訂單號等都是由這個機制生成並維護的。
“一個entity是否可以有n個標識符”?答案是否定的。只能有一個。標識符用來唯一標識一個獨立的實體。我上面所說的兩個值,其中一個是技術架構層面的,在領域模型中,一個實體只能有一個標識符。
不知我上面的回答是否能夠幫到你。PS:最近沒時間更新,不過我會爭取儘快更新。

出自:http://www.cnblogs.com/daxnet/archive/2010/07/07/1772615.html

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