我應該將 DTO 映射到客戶端和伺服器端的域實體/從域實體映射嗎?
我有一個豐富的域模型,其中大多數類都有一些行為和一些屬性,這些屬性要麼是計算出來的,要麼是暴露成員對象的屬性(也就是說,這些屬性的值永遠不會被持久化)。
我的客戶只通過 WCF 與伺服器對話。
因此,對於每個域實體,我都有一個相應的 DTO——一個僅包含數據的簡單表示——以及一個映射器類,它實現
DtoMapper<DTO,Entity>並可以通過靜態網關將實體轉換為其 DTO 等效項,反之亦然:var employee = Map<Employee>.from_dto<EmployeeDto>();這個應用程序的伺服器端主要是關於持久性,我的 DTO 從 WCF 服務進來,被反序列化,然後任意 ORM 將它們持久化到數據庫,或者查詢請求來自 WCF,ORM 執行該查詢DB 並返回要序列化並由 WCF 發回的對象。
鑑於這種情況,將我的持久性儲存映射到域實體是否有意義,還是應該直接映射到 DTO?
如果我使用域實體,流程將是
- 客戶端請求對象
- WCF 向伺服器發送請求
- ORM 查詢數據庫並返回域實體
- 映射器將域實體轉換為 DTO
- WCF序列化DTO並返回給客戶端
- 客戶端反序列化 DTO
- DTO通過mapper轉化為領域實體
- 創建的視圖模型等
回程類似
如果我直接映射到 DTO,我可以消除每個對象、每個請求的一個映射。我這樣做會失去什麼?
唯一想到的是在插入/更新之前進行驗證的另一個機會,因為我不能保證 DTO 在通過網路發送之前是否經過驗證甚至作為域實體存在,我想有機會在選擇時驗證(如果另一個程序可能在數據庫中放置了無效值)。還有其他原因嗎?這些理由是否足以保證額外的映射步驟?
編輯:
我確實在上面說過“任意 ORM”,並且我確實希望事情盡可能與 ORM 和持久性無關,但是如果您有任何特殊的東西要添加到特定於 NHibernate 的內容,請務必這樣做。
我個人建議將您的映射保留在伺服器端。你可能已經做了很多工作來建構你的設計,直到現在。不要把它扔掉。
考慮一下什麼是 Web 服務。它不僅僅是對您的 ORM 的抽象;這是一份合同。它是供內部和外部客戶使用的公共 API。
公共 API 應該沒有任何改變的理由。除了添加新的類型和方法之外,幾乎所有對 API 的更改都是重大更改。但是您的域模型不會那麼嚴格。當您添加新功能或發現原始設計中的缺陷時,您需要不時更改它。您希望能夠確保對內部模型的更改不會通過服務合同引起級聯更改。
出於類似的原因,為每條消息創建特定的類
Request實際上是一種常見的做法(我不會用“最佳做法”這個詞來侮辱讀者) ;Response擴展現有服務和方法的功能變得更加簡單,而無需進行重大更改。客戶可能不想要您在服務內部使用的完全相同的模型。如果您是您唯一的客戶,那麼這似乎是透明的,但是如果您有外部客戶並且已經看到他們對您的系統的解釋通常有多遠,那麼您就會了解不允許您的完美模型洩漏的價值超出服務 API 的範圍。
有時,甚至無法通過 API 將模型發回。發生這種情況的原因有很多:
- 對像圖中的循環。在 OOP 中非常好;連載的災難。您最終不得不對圖形必須在哪個“方向”進行序列化做出痛苦的永久選擇。另一方面,如果您使用 DTO,則可以在任何適合手頭任務的方向上進行序列化。
- 嘗試在 SOAP/REST 上使用某些類型的繼承機制充其量只能是一個雜項。舊式 XML 序列化器至少支持
xs:choice;DataContract沒有,而且我不會對基本原理爭論不休,但只要說您的富域模型中可能有一些多態性就足夠了,並且幾乎不可能通過 Web 服務來引導它。- 延遲/延遲載入,如果您使用 ORM,您可能會使用它。確保它被正確序列化已經很尷尬了——例如,使用 Linq to SQL 實體,WCF 甚至不會觸發惰性載入器,它只會放入
null該欄位,除非你手動載入它——但問題變得更糟數據返回。像List<T>在建構子中初始化的自動屬性這樣簡單的東西 - 在域模型中很常見 - 在 WCF 中根本不起作用,因為它不會呼叫您的建構子。相反,您必須添加一個[OnDeserializing]初始化方法,並且您真的不想用這些垃圾來弄亂您的域模型。- 我也剛剛注意到您使用 NHibernate 的括號中的註釋。考慮到像這樣的介面
IList<T>根本無法通過 Web 服務進行序列化!如果您像我們大多數人一樣將 POCO 類與 NHibernate 一起使用,那麼這根本行不通。當您的內部域模型根本不符合客戶的需求時,也可能有很多實例,並且更改您的域模型以適應這些需求是沒有意義的。作為一個例子,讓我們以發票這樣簡單的事情為例。它需要顯示:
- 賬戶資訊(賬號、姓名等)
- 發票特定數據(發票編號、日期、到期日等)
- 應收賬款級別資訊(前餘額、滯納金、新余額)
- 發票上所有內容的產品或服務資訊;
- 等等。
這可能很適合域模型。但是,如果客戶想要執行顯示其中 1200 份發票的報告怎麼辦?某種和解報告?
這對於序列化來說很糟糕。現在您要發送 1200 張發票,這些發票一遍又一遍地序列化**相同的數據 - 相同的帳戶、相同的產品、相同的應收帳款。**在內部,您的應用程序正在跟踪所有連結;它知道 Invoice #35 和 Invoice #45 是針對同一客戶的,因此共享一個
Customer參考;所有這些資訊在序列化時都會失去,您最終會發送大量冗餘數據。您真正想要的是發送一個自定義報告,其中包括:
- 報告中包含的所有賬戶及其應收賬款;
- 報告中包含的所有產品;
- 所有發票,僅帶有產品和帳戶 ID。
如果要避免大量冗餘,則需要在將傳出數據發送到客戶端之前對其執行額外的“規範化”。這非常有利於 DTO 方法;在你的領域模型中使用這種結構是沒有意義的,因為你的領域模型已經以自己的方式處理了冗餘。
我希望這些是足夠的範例和足夠的理由來說服您保持域 <–> 服務合同的映射完好無損。到目前為止,你所做的事情絕對是正確的,你有一個很棒的設計,如果你放棄所有這些努力來支持可能會在以後導致嚴重頭痛的事情,那將是一種恥辱。