本文將介紹DDD分層架構中普遍使用的數據傳輸對象Dto,而且與領域實體Entity,查詢實體QueryObject,視圖實體ViewModel等幾種實體進行比較。前端
當你閱讀我或其它博主提供的示例代碼時,會發現幾種類型的實體,這幾種實體初步看上去區別不大,只是名稱不一樣,特別在這些示例很是簡單的狀況下更是如此。你可能會疑惑爲什麼要搞得這麼複雜,採用一種實體不是更好?程序員
在最理想的狀況下,咱們只想採用領域實體Entity進行全部的操做。ajax
領域實體是領域層的核心,是業務邏輯的主要放置場所。換句話說,領域實體中包含了大量業務邏輯方法。json
若是領域實體中的屬性都包含getter和setter,而且全部屬性都是public的,那麼,使用這個Entity的程序員可能會繞過業務方法,直接操做屬性進行賦值。網絡
爲屬性直接賦值,是面向數據的過程式思惟,而調用方法是面向對象的方式,這也是領域模型的核心所在。架構
因此爲了強制實施業務規則,必須把業務方法操做過的屬性的setter訪問器隱藏起來,不然這個方法不會有人調用。app
當領域實體某些屬性的setter被隱藏後,直接在表現層操做領域實體將變得困難,由於Mvc或Wpf的模型綁定只能操做public的屬性。框架
哪怕你的系統沒有使用分佈式,好比只是一個Mvc網站,但因爲前端要求愈來愈高,客戶端不少時候須要經過ajax與服務端進行交流,通常採用json格式傳遞數據,這就要求你的實體可以序列化。分佈式
對領域實體進行序列化,首先須要考慮的問題是,可能序列化一個較大的對象圖,從而致使沒必要要的開銷。ide
領域實體通常包含導航屬性指向其它領域實體,其它的領域實體可能包含更多導航屬性,從而組成一個對象圖。若是採用Serializable特性進行序列化,而且沒有指定其它序列化選項,可能致使把一個龐大的對象圖序列化並進行網絡傳輸。
另外一個問題是,複雜的領域實體可能包含循環引用,從而致使序列化失敗。
對於序列化,一個更好的選擇是採用DataContract特性,被DataContract修飾過的類成員,不會被自動序列化,必須在成員上明確指定DataMember特性。
DataMember在必定程度上能夠緩解上述問題,好比減小須要序列化的數據,不序列化循環引用的對象等,但沒法從根本上解決問題。
對於不一樣的客戶端,可能須要的數據和格式不一樣,這屬於應用層需求,而領域實體只有一個,在領域實體上經過標記DataMember進行序列化費力不討好,沒法知足複雜的應用需求。
哪怕你只有一個Mvc網站,若是頁面上須要顯示一些領域實體不存在的數據,你根據這個需求,直接在領域實體上增長屬性是很是糟糕的作法,會嚴重污染你的領域模型,將大大下降領域實體的複用能力。
從以上能夠看出,對於一個比較複雜的系統,單憑領域實體很難完成任務,將太多的職責強加到領域實體上,會致使領域實體嚴重變形。
數據傳輸對象,即Data Transfer Object,簡稱DTO。
一個爲了減小方法調用次數而在進程間傳輸數據的對象,《企業應用架構模式》如是說。
能夠看出,DTO用於分佈式環境,主要用來解決分佈式調用的性能問題。同一進程內的對象調用,速度是很是快的,但跨進程調用,甚至跨網絡調用,性能降低N個數量級。爲了提高性能,須要減小調用次數,這就要求把屢次調用的結果打包成一個對象,在一次調用中返回儘可能多的數據。
上面是DTO的原始含義,下面來看看個人山寨用法。
雖然我也取名爲DTO,但個人動機並不徹底是一次打包更多數據來提高性能,而是解決上面提到的幾個問題,固然它們之間有必定關係,能夠看做一種變種用法。
DTO是一個貧血對象,也就是它裏面基本沒有方法,只有一堆屬性,而且全部屬性都具備public的getter和setter訪問器。
DTO擁有public的setter訪問器,方便的解決了表現層的模型綁定問題。
因爲DTO不執行業務操做,僅用於傳遞數據,因此不該該定義很是複雜的對象引用關係,這樣就避免了循環引用,解決了對象序列化的問題。
DTO能夠根據應用需求定義成不一樣的粒度,在通常狀況下,DTO是聚合粒度,也就是說,一個領域層的聚合對應一個DTO,這樣作的一個好處是方便對CRUD操做進行抽象以及代碼生成。
界面若是想保持簡單,應該儘可能一個界面操做一個聚合,將聚合的數據映射到DTO後,傳給視圖展現。
對於更加複雜的界面,須要在一個界面操做多個聚合,這種狀況下,把須要的所有數據打包到DTO進行操做。
從以上介紹中,你應該瞭解DTO不能理解爲單表操做,它能夠包含你須要的所有數據。
DTO處於應用層,在表現層與領域層之間傳遞數據。
DTO由應用層服務使用,應用層服務從倉儲中得到聚合,並調用DTO轉換器將聚合映射爲DTO,再將DTO傳遞給表現層。
關於應用層服務,後續再專門介紹。
聚合與DTO的轉換,看上去是一個簡單問題,在聚合與DTO幾乎徹底一致的狀況下,採用映射組件將很是省力。不少人採用AutoMapper,但它的性能稍微差了點,EmitMapper是更好的選擇,性能接近硬編碼。
當DTO與聚合顯著不一樣時,我發現手工編碼更加清晰高效。我採用代碼生成器建立出一個代碼基礎,在有個性化需求時,手工修改映射代碼。
我老是採用一個靜態類來擴展DTO和聚合,爲它們添加相關的轉換方法。
using Biz.Security.Domains.Models; using Util; namespace Biz.Security.Services.Dtos { /// <summary>
/// 應用程序數據傳輸對象擴展 /// </summary>
public static class ApplicationDtoExtension { /// <summary>
/// 轉換爲應用程序實體 /// </summary>
/// <param name="dto">應用程序數據傳輸對象</param>
public static Application ToEntity( this ApplicationDto dto ) { return new Application( dto.Id.ToGuid() ) { Code = dto.Code, Name = dto.Name, Note = dto.Note, Enabled = dto.Enabled, CreateTime = dto.CreateTime, Version = dto.Version, }; } /// <summary>
/// 轉換爲應用程序數據傳輸對象 /// </summary>
/// <param name="entity">應用程序實體</param>
public static ApplicationDto ToDto( this Application entity ) { return new ApplicationDto { Id = entity.Id.ToString(), Code = entity.Code, Name = entity.Name, Note = entity.Note, Enabled = entity.Enabled, CreateTime = entity.CreateTime, Version = entity.Version, }; } } }
ViewModel是爲特定視圖專門定義的實體對象,專爲該視圖服務。
對於WPF,ViewModel是必須的,用來支持MVVM模式進行雙向綁定。
那麼MVC呢,必定須要它嗎?
因爲採用了DTO,在通常狀況下,我都把這個DTO看成ViewModel來使用。若是界面上須要某個屬性,我會直接添加到DTO上。
一個例外是,若是MVC的界面很是複雜,我感受把大量的垃圾屬性加到DTO上不合適,就會建立專門的ViewModel。
查詢實體這個說法,是我亂取的,估計你在其它地方也沒有據說過。使用它的緣由,是用來配合個人查詢組件一塊兒工做。
我前面已經介紹過查詢相關的內容,核心思想是經過判斷一個可空屬性,自動完成空值判斷,這是一個強大的特性,幫助你免於編寫大量雜亂無章的判斷。
查詢實體的基本特徵就是全部屬性必須可空,而且它足夠簡單,不會擁有集合那樣的子對象,全部屬性都是扁平化的。
經過傳遞查詢實體,表現層能夠作到儘可能簡單,因爲表現層支持模型綁定,甚至不須要代碼,省力是我搭建框架的一個基本出發點。
固然查詢實體只支持簡單查詢,不支持靈活的動態查詢,好比讓客戶設置查詢運算符等,暫時沒有這方面的需求,若是後續有需求,會擴展一個出來。
查詢實體示例:
using System.ComponentModel.DataAnnotations; using Util; using Util.Domains.Repositories; namespace Biz.Security.Domains.Queries { /// <summary>
/// 應用程序查詢實體 /// </summary>
public class ApplicationQuery : Pager { /// <summary>
/// 應用程序編號 /// </summary>
[Display( Name = "應用程序編號" )] public System.Guid? ApplicationId { get; set; } private string _code = string.Empty; /// <summary>
/// 應用程序編碼 /// </summary>
[Display( Name = "應用程序編碼" )] public string Code { get { return _code == null ? string.Empty : _code.Trim(); } set { _code = value; } } private string _name = string.Empty; /// <summary>
/// 應用程序名稱 /// </summary>
[Display( Name = "應用程序名稱" )] public string Name { get { return _name == null ? string.Empty : _name.Trim(); } set { _name = value; } } private string _note = string.Empty; /// <summary>
/// 備註 /// </summary>
[Display( Name = "備註" )] public string Note { get { return _note == null ? string.Empty : _note.Trim(); } set { _note = value; } } /// <summary>
/// 啓用 /// </summary>
[Display( Name = "啓用" )] public bool? Enabled { get; set; } /// <summary>
/// 起始建立時間 /// </summary>
[Display( Name = "起始建立時間" )] public System.DateTime? BeginCreateTime { get; set; } /// <summary>
/// 結束建立時間 /// </summary>
[Display( Name = "結束建立時間" )] public System.DateTime? EndCreateTime { get; set; } /// <summary>
/// 添加描述 /// </summary>
protected override void AddDescriptions() { base.AddDescriptions(); AddDescription( "應用程序編號", ApplicationId ); AddDescription( "應用程序編碼", Code ); AddDescription( "應用程序名稱", Name ); AddDescription( "備註", Note ); AddDescription( "啓用", Enabled.Description() ); AddDescription( "起始建立時間", BeginCreateTime ); AddDescription( "結束建立時間", EndCreateTime ); } } }
最後來總結一下:
1. 領域實體是系統的中心,是業務邏輯的主要放置場所,應該儘可能關閉業務邏輯操做的屬性,以免有人能繞過你的方法直接操做數據。
2. DTO是數據傳輸對象,原義是用來在分佈式系統中一次傳輸更多數據,以減小調用次數,提高性能。
3. 個人DTO用法離原義相去甚遠,只是借用了DTO的名詞,屬於變種。DTO爲我解決了以下幾個問題:
4. DTO是包含大量屬性,沒有方法的貧血實體,全部屬性都開放getter和setter,以方便模型綁定和序列化。
5. DTO通常狀況下是聚合去除方法後的模樣,主要好處是方便抽象CRUD及代碼生成。
6. DTO位於應用層,由應用層服務操做它。
7. DTO的映射能夠採用映射組件,也能夠代碼生成方便隨時修改,以你以爲方便爲主。
8. 僅在WPF環境下才須要爲每一個視圖建立一個對應的ViewModel,MVC通常使用DTO便可,僅爲複雜界面建立ViewModel。
9. 查詢實體是爲了配合查詢組件引入的構造,目的是幫助查詢組件完成空值判斷,而且簡化表現層的調用。
本文分享了我在幾個構造類型上的認識和經驗,但願你們積極討論,更但願高手能指正個人不足,幫助我與你們一塊兒進步。
.Net應用程序框架交流QQ羣: 386092459,歡迎有興趣的朋友加入討論。
.Net Easyui開發交流QQ羣(本羣僅限Easyui開發者,非Easyui開發者勿進):157809322
謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/xiadao521/