2011-02-07 169 views
5

我是衆多試圖理解聚合根的概念之一,我認爲我已經掌握了它! 但是,當我開始建模這個示例項目時,我很快陷入了困境。簡單的聚合根和存儲庫

我有兩個實體ProcessTypeProcess。 A Process不能存在沒有ProcessType,和ProcessType有許多Process es。所以一個進程擁有一個類型的引用,沒有它就不能存在。

那麼應該ProcessType是一個聚合根?新進程將通過調用processType.AddProcess(new Process())創建; 但是,我有其他實體僅保存對Process的引用,並通過Process.Type訪問其類型。在這種情況下,首先通過ProcessType是沒有意義的。

但是聚合之外的AFAIK實體只允許持有對聚合根目錄的引用,而不對聚合內部的實體進行引用。那麼我在這裏有兩個聚合體,每個都有自己的存儲庫?

回答

13

我基本上什麼西西弗斯曾表示同意,特別是有關不收縮自己的「規則的位'的DDD,可能會導致一個非常不合邏輯的解決方案。

就你的問題而言,我遇到過很多次,我將'ProcessType'定義爲查找。查找是「定義」的對象,並且具有對其他實體的引用;在DDD術語中,它們是價值對象。我認爲查找的其他例子可能是團隊成員的'RoleType',例如可能是測試人員,開發人員和項目經理。即使是一個人的「標題」我會定義爲查找 - 先生,小姐,太太,博士

我會爲你的過程骨料模型:

public class Process 
{ 
    public ProcessType { get; } 
} 

正如你所說的,這些類型的對象通常需要在UI中填充下拉列表,因此需要他們自己的數據訪問機制。但是,我個人並沒有爲他們創建'存儲庫',而是'LookupService'。這對我來說保留了DDD的優雅,通過嚴格保持「存儲庫」的聚合根。

這裏是我的應用程序服務器上的命令處理程序的一個例子,以及如何我已經實現了這一點:

團隊成員總結:

public class TeamMember : Person 
{ 
    public Guid TeamMemberID 
    { 
     get { return _teamMemberID; } 
    } 

    public TeamMemberRoleType RoleType 
    { 
     get { return _roleType; } 
    } 

    public IEnumerable<AvailabilityPeriod> Availability 
    { 
     get { return _availability.AsReadOnly(); } 
    } 
} 

命令處理程序:

public void CreateTeamMember(CreateTeamMemberCommand command) 
{ 
    TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID); 

    TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID, 
                  role, 
                  command.DateOfBirth, 
                  command.FirstName, 
                  command.Surname); 

    using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork()) 
     _teamMemberRepository.Save(member); 
} 

客戶端也可以使用LookupService來填充下拉列表等:

ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>(); 
10

不是那麼簡單。 ProcessType最像是一個知識層對象 - 它定義了一個特定的過程。另一方面,流程是一個ProcessType實例。您可能真的不需要或不需要雙向關係。進程可能不是ProcessType的邏輯子進程。它們通常屬於其他內容,如產品,工廠或序列。

另外根據定義,當你刪除一個聚合根時,你刪除了聚合的所有成員。當你刪除一個進程時,我真的懷疑你真的想刪除ProcessType。如果您刪除了ProcessType,您可能需要刪除該類型的所有進程,但是這種關係已經不夠理想,並且只要您有一個由ProcessType定義的歷史進程,就不會刪除定義對象。

我會從ProcessType中移除Processes集合,並找到更合適的父級(如果存在)。我會保留ProcessType作爲Process的成員,因爲它可能定義了Process。操作層(Process)和知識層(ProcessType)對象很少作爲單個聚合體工作,因此我可以將Process作爲聚合根或可能找到作爲進程父級的聚合根。那麼ProcessType將是一個外部類。由於您已經擁有Process.ProcessType,Process.Type很可能是多餘的。只是擺脫這一點。

我有一個類似的保健模型。有過程(操作層)和過程類型(知識層)。 ProcedureType是一個獨立的類。過程是第三個對象Encounter的子項。 Encounter是Procedure的聚合根。過程有對ProcedureType的引用,但它是一種方法。 ProcedureType是一個定義對象,它不包含一個Procedures集合。

EDIT(因爲評論是如此有限)

一件事,通過這一切要牢記。許多人都是DDD純粹主義者,堅守規則。然而,如果你仔細閱讀埃文斯,他會不斷提出經常需要權衡的可能性。爲了方便起見,他還花費了相當多的時間去描述邏輯和經過深思熟慮的設計決策,比如不瞭解目標的團隊或者爲了方便而繞過聚合等事情。

重要的是理解和應用概念,而不是規則。我看到很多DDD是鞋拔子應用到不合邏輯和混亂骨料等沒有其他原因,而不是因爲對庫或遍歷被應用字面規則,這不是DDD的意圖,但它往往是過於武斷的做法許多產品採取。

那麼什麼是關鍵的概念在這裏:

聚集提供使通過減少許多對象的行爲成爲關鍵球員更高級別行爲的複雜系統更易於管理的一種手段。

聚集提供了一種方法來確保對象在還保留跨更新和刪除工作的一個邏輯單元的邏輯總是有效的條件創建。

讓我們考慮的最後一個點。在許多傳統應用程序中,有人創建了一組未完全填充的對象,因爲它們只需要更新或使用一些屬性。下一個開發者出現了,他也需要這些對象,並且有人已經在鄰居的某個地方爲了不同的目的製作了一個集合。現在這個開發者決定只使用這些,但他然後發現他們沒有他需要的所有屬性。所以他添加了另一個查詢並填充了更多的屬性。最終,因爲團隊不堅持OOP,因爲他們採取OOP「對現實世界無效且不切實際並導致性能問題(例如創建完整對象來更新單個屬性)的共同態度」。他們最終得到的是一個充滿嵌入式SQL代碼和對象的應用程序,它們基本上隨機地在任何地方實現。更糟糕的是這些對象是混雜無效的代理。一個過程似乎是一個過程,但它不是,它根據需要以任何給定的點以不同的方式部分填充。最終會出現無數查詢的球泥,以不同程度連續部分填充對象,並經常出現許多不應該存在但因爲對象從未真正有效而需要的空檢查等外來廢話。

聚合規則通過確保對象僅在特定邏輯點創建並始終具有一整套有效關係和條件來防止這種情況。所以現在我們已經完全明白了什麼樣的聚合規則以及他們保護我們的原因,我們也想明白,我們也不想濫用這些規則,並創建奇怪的聚合體,這些聚合體並不能反映我們的應用程序實際上只是因爲什麼這些總規則存在並且必須隨時遵守。

所以,當埃文斯說,創建庫只爲聚集他說處於有效狀態,創造聚集,並保持他們的方式,而不是直接繞過內部對象的總和。你有一個Process作爲一個根集合,所以你創建一個存儲庫。 ProcessType不是該聚合的一部分。你是做什麼?那麼如果一個對象本身就是一個實體,它就是一個聚合體。你爲它創建一個存儲庫。

現在最純粹的會來,說你不應該有一個倉庫,因爲ProcessType是一個值對象,而不是一個實體。因此ProcessType根本不是一個聚合,因此您不需要爲其創建存儲庫。所以你會怎麼做?你沒有做的是將shoehorn ProcessType轉換成某種人造模型,除了你需要獲取它以外,沒有其他的原因,所以你需要一個倉庫,但有一個倉庫你必須有一個實體作爲一個聚合根。你所做的是仔細考慮這些概念。如果有人告訴你存儲庫是錯誤的,但是你知道你需要它並且他們可能會這樣說,那麼你的存儲庫系統是有效的並且保留了關鍵概念,所以保持存儲庫原樣而不是讓你的模型滿足教條。

現在,在這種情況下,假定我對ProcessType什麼是正確的,因爲其他的評註者指出它實際上是一個值對象。你說它不可能是一個價值對象。這可能有幾個原因。也許你會這樣說,因爲你使用NHibernate的例子,但NHibernate模型用於在與另一個對象相同的表中實現值對象不起作用。所以你的ProcessType需要一個標識列和字段。通常由於數據庫考慮,唯一實際的實現是在它們自己的表中使用帶ID的值對象。或者你也可以這樣說,因爲每個Process都通過引用指向一個ProcessType。

沒關係。由於這個概念,它是一個有價值的對象。如果有10個Process對象具有相同的ProcessType,那麼您有10個Process.ProcessType成員和值。無論每個Process.ProcessType指向一個單一的參考,或每個拿到拷貝,仍應按照定義都是完全一樣的東西,都可以與任何其他的10是什麼使得它的值對象完全互換。因此,說「它有一個身份證明不能成爲一個有價值的物體,你有一個實體」正在犯一個教條性的錯誤。不要犯同樣的錯誤,如果你需要一個ID字段給它一個,但不要說「它不可能是一個值對象」時,它實際上是一個儘管對於其他原因,你不得不放棄一個Id至。

那麼,如何獲得這一個對與錯? ProcessType是一個Value對象,但由於某種原因,您需要它擁有一個Id。 Id本身不違反規則。通過擁有10個全部具有完全相同的ProcessType的進程,您可以做到這一點。也許每個人都有一個本地深入的副本,也許他們都指向一個對象。但是每種方法都是相同的,例如,每個人都有一個Id = 2。當你這樣做的時候你會得到錯誤的:10每個進程都有一個ProcessType,這個ProcessType是相同的,並且完全可以互換,除了現在每個進程也都有它自己的唯一標識符。現在你有10個同樣的事情,但他們只在Id中有所不同,並且只會在Id中變化。現在你不再有一個值對象,而不是因爲你給它一個ID,而是因爲你用的是反映了實體的性質實現了它的標識 - 每個實例都是獨特的,不同的

有意義嗎?

+0

非常感謝 - 你是對的。但根據埃文斯在他的藍皮書中,他說你創建了聚合的存儲庫。但是,在我的方案中,我需要獨立類(ProcessType)的存儲庫,因爲我需要能夠創建新的過程類型。並不是它會經常發生,但它是一種選擇。你將如何處理? – Vern 2011-02-07 16:23:16

+0

通過創建,我的意思是堅持在數據庫中的對象 - 爲此我使用存儲庫模式。存儲庫不負責創建對象。 – Vern 2011-02-07 18:33:04

+0

當使用基於知識層的設計時,考慮知識創建本身是否應該成爲主要應用的一部分。依靠。在我的情況下,有數百萬的程序和症狀。需要一個不在系統中?有一個應用程序。還有一位高級醫生開始使用它。因此,我們有一位新患者吃肉d蓉,這是已知病例中的1例。頂端的人會說這是一個寫作。醫生小便和呻吟,但他們的方式會使他們自己的系統在一個月內無法使用。不同的問題得到不同的應用程序不屬於主要部分。 – Sisyphus 2011-02-07 19:32:52

0

看,我認爲你必須調整你的模型。像使用Value對象和Process Agg Root一樣使用ProcessType。 這樣,每一道工序都有processType

Public class Process 
{ 
     Public Process() 
     { 

     } 

     public ProcessType { get; } 

} 

爲這款U只需1根AGG不2.