簡體   English   中英

DTO應該由域實體還是持久性生成?

[英]Should a DTO be generated by a domain entity or from persistence?

當談到具有現代ORM的分層應用程序時,我常常不確定如何創建特定類以遵守所謂的“最佳實踐”,同時還要關注性能要求。

考慮到應用程序中可能包含以下任意類型的對象:

  1. 域實體 - 這些是包含業務邏輯的豐富類(對嗎?),並且根據ORM功能,可能與持久性設計直接相關。

  2. DTO - 這些是更簡單的類,它們剝離業務邏輯,以便將數據傳遞給內部和外部客戶端。 有時這些是扁平化的,但並非總是如此。

  3. 視圖模型 - 這些與DTO類似,因為它們更簡單,沒有業務邏輯,但它們通常非常扁平,並且通常包含與它們所服務的UI相關的其他位。

我遇到的挑戰是,在某些情況下,域實體或任何面向持久性的類映射到更簡單的實體(如DTO或ViewModel)會阻止您進行重要的性能優化。

例如:

假設我有一些域實體看起來像這樣:

public class Event
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime EventDate { get; set; }

    // These would be reference types in most ORMs
    // Pretend in the setter I have logic to ensure the headliner =/= the opener
    public Band Headliner { get; set; }
    public Band Opener { get; set; }
}

public class Band
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Genre Genre { get; set; }
}

在現實世界中,這些可能要復雜得多,具有各種業務邏輯,可能還有一些驗證調用等。

如果我公開了一個公共API,我的DTO可能看起來非常像這個例子,沒有任何業務邏輯。

如果我還有一個MVC網絡應用程序,我想要顯示一個事件列表,我可能想要一個看起來像這樣的視圖模型:

public class EventViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime EventDate { get; set; }

    public int HeadlinerId { get; set; }
    public string HeadlinerName { get; set; }
    public int OpenerId { get; set; }
    public string OpenerName { get; set; }
}

通常,人們只需使用引用來提取完整​​的域實體,然后使用映射實用程序來水合視圖模型。

但是,假設我有成千上萬的記錄。 現在,ORM可能會創建一個查詢風暴來填充完整的引用對象(這可能比這個示例復雜得多,有自己的引用)。 性能開始嚴重受損並不需要很長時間。

問題是什么?

我知道我不是唯一遇到這個問題的人,所以我很想知道人們如何維護分層應用程序,同時仍然需要在生成代表相同底層域信息的多個對象時保持性能。

讓兩個Event -ish對象表示相同的持久化數據感覺不對,但同時看起來持久層看起來不應該知道DTO或視圖模型,否則爭取分離的重點是什么?

那你怎么解決這個問題呢? 持久性是否了解域實體的嚴格,詳細表示以及這些實體中數據的輕量級描述? 是那些重量較輕的描述DTO或某些域實體精簡版?

您的問題沒有簡單的答案,因為它實際上取決於您希望通過您的架構實現的目標。 這是一種經典的架構權衡。

這也意味着你需要自己決定。 確保您了解每種方法的優缺點,然后決定您的項目。 以下是優缺點列表:

嚴格分離的優點

  • 能夠根據特定層的職責調整和調整結構。 例如,持久性DTO可以以不同於域實體的方式存儲數據以支持復雜的查詢案例。
  • 能夠支持數據遷移案例。 使用單獨的持久性DTO,您可以選擇加載“舊”DTO格式並將其轉換為“新”域實體。
  • 能夠簡化返回到外部世界的DTO,例如通過API。 這在使用DDD時幾乎總是有意義,因為使用DDD通常表明域很復雜。
  • 更好地分離開發人員的顧慮。 通常,嚴格的分層會增加團隊並行處理相同功能的可能性,例如持久性中的一個和域中的一個。
  • 根據ORM或數據庫的功能集,在持久性中直接使用域實體甚至不是一個選項。 如果是一種選擇,它可能比擁有專用的DTO更復雜。

共享類的優點

  • 減少相同功能的代碼。
  • 通常可以更快地開發新功能。
  • 較小的概念開銷。 我認為這是一個小問題,因為DTO和視圖模型是眾所周知的概念,但它可能是一個問題,取決於團隊。

如您所見,我認為性能不是共享方法的優勢。 主要原因是精心設計的對象到對象映射的數量級比從數據庫加載數據的速度快。 所以我非常有信心嚴格分離方法中的性能問題是由於其他問題,而不是分層。

有了以上幾點(可能還有更多針對您的環境的內容),您應該能夠做出決定。 我過去曾使用過這兩種方法,但對於一定規模的項目,我總是選擇嚴格的分離方法。

喬希,

域實體必須獨立於ORM,事實上,如果您遵循DDD原則,所有域層都不應該依賴於任何其他層。 DTO只是在層之間傳遞數據,在大多數情況下,它在Repository的接口中使用,作為方法的返回。 並且Repository的接口(作為Service)應該保留在Domain層中。

讓兩個Event-ish對象表示相同的持久化數據感覺不對,...

實際上,這並不一定是壞事。 您的EventViewModel 最終可能與您的Event一致 您的Event確保滿足所有Event業務規則,而您的EventViewModel可能通過偵聽由(例如) Event類發出的域事件來更新。 這有時稱為投影 - 一個EventViewModelProjection監聽Event域事件(沒有雙關語)並在EventViewModels上投影。

但與此同時,持久層似乎不應該知道DTO或視圖模型,......

好吧,如果你選擇堅持DTO並查看模型,那么應該在某處對持久性邏輯進行編碼。

否則,爭取分離的重點是什么......那么你如何解決這個問題......那些重量較輕的描述是DTO還是某些領域的實體?

不可能給出一個明確的答案 - 這些都是設計考慮因素,具體取決於您的具體情況。 如果遇到性能問題,那么使用我提到的域事件可能是一個好主意。

您可能有興趣閱讀有關cqrs最終一致性的內容以獲得一些想法。

DTO沒有行為,它們用於數據傳輸。

ViewModels包含有關演示文稿的一些行為,因此它們也不是DTO。 如果您沒有任何特定於視圖的行為,則可以在演示文稿中使用DTO。

域實體和ORM實體不相同。 你在做什么可能是活躍的記錄,而不是域模型。 您應該能夠用您喜歡的持久性邏輯替換ORM。 他們必須脫鈎。

我認為你將DTO與值對象混淆,后者是業務對象,實際上有行為並且是持久的。 如果它沒有標識並且它包含屬於一起的行為或多個值,那么通常使用值對象描述某些內容。 例如,地址,電話號碼,id等可以是值對象。

您不能使用域對象將數據傳輸到演示文稿。 演示文稿必須無法直接訪問和修改域對象,這就是我們使用DTO在演示文稿和應用程序服務之間發送數據的原因。 應用程序服務可以訪問域對象。

通常,人們只需使用引用來提取完整​​的域實體,然后使用映射實用程序來水合視圖模型。

但是,假設我有成千上萬的記錄。 現在,ORM可能會創建一個查詢風暴來填充完整的引用對象(這可能比這個示例復雜得多,有自己的引用)。 性能開始嚴重受損並不需要很長時間。

要最大限度地減少查詢次數並提高性能:

  1. 在一次調用中請求多個對象(例如,100個事件)

  2. 在一次調用中請求與主對象相關的對象(例如,100個帶有頂棚和開啟器的事件)

  3. 緩存對象以查找已請求的對象,而不是再次請求

  4. 來自ViewModel的隊列請求(每個ViewModel告訴它需要哪些對象,然后在一次調用中請求所有對象,並且每個ViewModel都會返回它要求的對象)

根據您要查詢的層, Object表示在域/持久層的上下文中服務層或Entity的上下文中的DTO

我認為這是您開始嘗試DDD時出現的第一個問題之一:查詢結束顯示數據時的性能。

這里的關鍵概念是域模型必須關注操作,實施業務規則並最終觸發事件,而不是提供信息。 當然,您仍然可以將其用作向用戶顯示的數據源,但是,如果您遇到性能問題,最好是評估使用命令查詢責任隔離模式( CQRS )。

有了它,要顯示的數據由另一個模型(特別是數據模型)表示,在您的示例中,它可以是EventViewModel類。 數據模型獨立於域模型而設計,通常以從數據源構建它的方式設計(即:不需要對象映射)。

域實體代表您的域/業務。 例如,在抵押域中,托管賬戶是域實體。 托管帳戶由其帳號標識。 此實體不代表您的表架構,這對您的數據庫一無所知。

public class Escrow
{
public Guid AccountId {get; set;}
public decimal GetBalance()
}

查看模型

我總是將視圖模型與Domain和DTO分開,因為我希望View Models能夠代表我的視圖而不是其他任何東西。 這將有數據注釋,驗證邏輯等。

DTO和OR​​M實體

現在這是棘手的一點。 我將DTO和OR​​M實體保留在同一個項目中,並確保它們對任何事物都沒有任何依賴關系,它們只是POCO。 我首先創建ORM實體,並在需要時添加或創建DTO。

我使用為層的ORM創建的實體,我盡可能將它們用作DTO。 我不向這些ORM實體添加或刪除屬性。 如果我需要與我的服務的ORM實體或我的應用程序中的任何其他層稍微不同的結構,我會為該要求創建新的POCO,並且它們可供所有層使用。

例如,如果我需要一個計算值,我想傳遞給ORM實體中不可用的UI層(因為它們沒有被保留),那么我創建一個只包含必需字段的新POCO。

我使用AutoMapper在對象之間復制數據

在域驅動設計(DDD)中,域層應該不知道持久性,表示,緩存,日志記錄和其他基礎結構服務。 這可以通過使用抽象(使用接口而不是依賴服務上的具體服務)來實現。 您可以應用SOLID原則來幫助您創建良好的軟件架構:

S是單一責任原則(SRP)
O代表開放閉合原理(OCP)
L Liskov替換原理(LSP)
我接口隔離原則(ISP)
D依賴注入原則(DIP)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM