【道德經】漫談實體、物件、DTO及AutoMapper的使用
寫在前面
實體(Entity)、物件(Object)、DTO(Data Transfer Object)資料傳輸物件,老生常談話題,簡單的概念,換個角度你會發現更多的東西。個人拙見,勿喜請噴。
實體和值物件
在常規開發中(事務指令碼),我們所說的實體只是一些資料庫對映的欄位,物件只不過是包含業務功能描述的集合而已,在DDD(領域驅動設計)中,實體(Entity)和值物件(Value Object)是基本元素之一,事務指令碼中所說的物件概念大概就是領域模型中領域(Domain)的概念了,但並不是只是業務功能描述的集合而已,不針對功能實現,而是針對業務協作完成的一種流程,DDD中的實體是一種領域物件,區別實體和值物件的方法就是判斷是否有唯一標示,而不是屬性,即使屬性完全相同也可能是兩個不同的物件。同時實體本身有狀態的,而且有自己的生命週期,實體本身會體現出相關的業務行為,業務行為會對實體屬性或狀態造成影響和改變。比如雙胞胎假設所有的屬性都一樣,仍然是兩個不同的人。從哲學角度講,這正是實體的本來含義。它是除了所有屬性之外還不足以表達的“那個”東西,不依賴其它而自存的東西。
如何區分實體和值物件,比如在城市社保系統中,參與社保人就是一個實體,在業務系統中,社保是一種概念,針對的是參與社保人,所以我們要在業務中來區分參與社保人,人有可能同名同姓,所以不能用名字來區分,這裡的名字就是參與社保人的一個屬性,所以我們用身份證號來區分參與社保人,這裡的身份證號就是參與社保人的唯一標示,用來說明:實體是什麼?實體是哪個?
還有有一種業務場景是這樣,比如在全國社保統計系統中,統計各個城市的參與社保的比率,因為我們只要知道這個人是不是參與了城市社保?而並不需要知道他是哪個人,所以這裡面的參與社保人就是一個值物件,只是用來說明:值物件是什麼?
可以看出區分實體和值物件只是在特定的業務場景下,同一種特定物件可能會有不同的方式看待,這裡面就是一個邊界的問題,而且特定的業務場景中的實體是有自己的狀態和生命週期,這和值物件也有明顯的區分。
實體和物件
道可道,非常道。名可名,非常名。 無名天地之始,有名萬物之母。故常無慾以觀其妙; 常有欲以觀其徼(jiào)。 此兩者同出而異名,同謂之玄,玄之又玄,眾妙之門。 --《道德經》
上面這段話出自老子的道德經的開篇,簡單說下前兩段話的意思:道似乎有具體的定義,但總不是我們所想象出的定義,名取出了一個名,但不一定我們會一直使用。下面幾段就是對有(名)和無(道)的辯證關係,最後得出:“無”是天地的來處,“有”是衍生萬物的結果,這兩者之間,同出為意義不一樣,同樣好似玄妙務必,無窮無盡,切是研究一切的門經。
為什麼會引用道德經?其實在我看來,老子不做軟體開發真是太虧了(哈哈),什麼是實體和物件?可能每個人都有自己的解讀,就像上面討論的實體和值物件,其實某種意義上來說應該是領域物件和物件屬性,這裡說物件屬性也並不是準確,就比如專案中我們使用的屬性字典,並不是任何一種物件的屬性,只是一個特定的值,不依附於任何物件。實體雖然稱作實體,其實是一個物件,值物件雖然稱作物件,但其實只是一個特定值,意義就像”道可道,非常道,名可名,非常名“一樣,
老子探討的有無關係,其實在軟體程式設計中就是實體和物件的關係,“有”可以看做是“實體”,“無”可以看做是“物件”,無衍生出有,有體現出無,就像實體和物件之間的關係,實體是不是物件?物件是不是實體?實體和物件到底什麼?這其實只是存在一個邊界問題。正如DDD中,實體即是領域物件,物件即是實體模型,我們生活中常常討論:“是先有的雞蛋?還是先有的雞?”最後都沒有得出一個準確的結果,如果老子來回答這個問題,就六個字:“雞生蛋,蛋生雞”,至於解釋,老子揮一揮衣袖,騎上青牛遠去-“自己去琢磨吧”。
故常無慾以觀其妙,常有欲以觀其徼
故常無慾以觀其妙,常有欲以觀其徼(jiào)。 --《道德經》
這段話我覺得是道德經開篇的精髓,你可能從字面上可以體會得到一些內容,這其實一種態度,一種生活態度,一種程式設計態度。
“故常無慾以觀其妙”,這句話在我們的現實生活中可以很好的去解讀,世間萬物是如此的大,我們還有很多的事物沒有去認知,所以我們就會抱著征服的慾望去探尋,看到美好的事物就想去掠奪佔有,就像當你偶然發現一朵非常漂亮的鮮花,很多人不會停留下去欣賞它,而是去採摘它,然後據為己有,生活中的例子比比皆是,就像文章會犯錯一樣。
老子的所提倡的就是我們應該保持“無慾”的心態去看待事件萬物,去觀察,去體會它的玄妙,上面所說的掠奪、佔有,就不是“觀”所蘊含的意義了。我們在做專案中,專案前期需求還沒有確定好就去開發,到最後弄得進退兩難,在DDD中,業務需求是很重要的一環,我們應該花更多的時間去了解它、體會它、確定它,而不是想當然的瞭解後就去開發專案,這也是建模專家所必備的基本條件。
“常有欲以觀其徼”,這句話的中的精髓就一個字“徼”,徼翻譯為邊界的意思,“有欲”在現實生活中可以指一些有名望、有地位、有財富的人,這些人當擁有了一些常人所不能擁有的東西后,並不懂得收斂和滿足,反而使自己的慾望心更大,想得到更大的滿足,得到後還想得到,沒有一個界限,最後的下場一般都是很慘,就像和珅貪得無厭一樣。
老子所提倡的就是我們在“有欲”之後,要觀察、體會一個界限,要使自己的“欲”控制在這個界限中,水滿則溢就是這個道理。就像在DDD中,實體和值物件邊界的確定一樣。
初始實體和演化實體
含德之厚,比於赤子。
專氣致柔,能如嬰兒乎?
為天下豁,常德不離,復歸於嬰兒。
--《道德經》
老子在道德經中多次提到有關嬰兒的話題,就像上面幾句,總是拿一些事物和嬰兒進行比較,難道說老子喜歡嬰兒?準確的應該說,老子推崇嬰兒的那種狀態,何種狀態?無慾無求、迴歸自然、保持天性。。。
人的進化史進行了千百萬年,從最初的簡單生存原則,發展到現在複雜的人世關係,越進化越複雜,導致我們現在越活越累。新出生的嬰兒沒有任何外界的摻雜,是如此的純淨,正如一碗清水一般,但隨著成長,慢慢的接觸外界事物,清水也會被染成五顏六色,而失去了本來固有的一些東西,這也就是為什麼老子推崇嬰兒的原因。有時候我們離開喧囂的城市,置身於寧靜的山坳,你會發現身心是如此的舒暢,其實這才是我們所固有的東西,只是處在亂世中,把那一抹清明掩蓋罷了。
什麼是初始實體和演化實體?這只不過是我自己定義的,這裡面的實體也可以看做是物件,只是在DDD中稱作為實體,如上面所說,嬰兒就像初始實體一樣,長大後的我們就是演化實體,初始實體只有一種狀態,也就是一種原始狀態或者稱作是無狀態,特定的場景下初始實體只有一個抽象出來的物件,但是演化實體有很多種,但都是從初始實體演化出來的,所以稱為演化實體,有直接的關係也有間接的關係,如果把嬰兒看做是初始實體,長大後的我們是演化實體,但演化實體並不只有長大後我們,汽車、成績單、衣服等等一些與我們相關的事物都可以稱為演化實體,但長大後的我們只是和初始實體有直接關係,其他的和初始實體都是間接關係,這個特定的場景就是人類進化史。
當然有人看到這可能有些想法,認為你這說的什麼亂七八糟的東西,沒有一點實際的意義。我的意思並不是說明初始實體和演化實體是個什麼東西,而是說在我們做專案的過程中要找到那個“初始實體”,比如物流業務系統場景,在這個系統中哪個是初始實體?排程?賬單?掃描?都不是,準確的說應該是運單,因為所有的業務操作都是圍繞它來展開,或者是由它演生而來,雖然初始實體我們找到了,但是要仔細的揣摩它,確定是出生的嬰兒還是長大後的我們?
聚合(Aggregate)和聚合根(Aggregate Root)是DDD中的重要概念,什麼是聚合?它通過定義物件之間清晰的所屬關係和邊界來實現領域模型的內聚,並避免了錯綜複雜的難以維護的物件關係網的形成,聚合定義了一組具有內聚關係的相關物件的集合,我們把聚合看作是一個修改資料的單元。如果把人類社會看做是領域模型,聚合看做是一個國家,國家中的人是一個實體,那這個國家的人就是一個聚合根,但是每個國家的人都是人,只不過膚色、語言、習俗會有些不同,可以把人看做這個場景中的初始實體,也就是初始聚合根,而不是國家的人。
程式碼中的DTO
DTO(Data Transfer Object)資料傳輸物件,注意關鍵字“資料”兩個字,並不是物件傳輸物件(Object Transfer Object),所以只是傳輸資料,並不包含領域業務處理,雖然用途只是傳輸資料,但本身其實也是物件,完成與領域物件之間的轉換,就像上面說的值物件一樣,某種意義上DTO可以看做是值物件的集合,只不過是和領域物件之間的對映,不包含任何的業務邏輯。
為什麼要使用DTO?主要原因是隔離Domain Model,使改動領域模型而不影響UI,還有就是保持領域模型的安全,不暴露業務邏輯。還有就是在分散式模式下,不同的場景使用相同的資料結構有不同的需求,而我們又不得不做一些資料轉化,這是很繁瑣的,如果我們在專案初期,做DTO的分析,這樣我們就會省很多的事,而且還不會影響整個專案的業務流程,也方便以後對專案進行擴充套件。
下面我們虛擬一個簡單“文章”領域模型:
1 public class Article : IEntity 2 { 3 public Article() 4 { 5 this.Id = Guid.NewGuid(); 6 } 7 public string Title { get; set; } 8 public string Content { get; set; } 9 public string Author { get; set; } 10 public DateTime PostTime { get; set; } 11 public string Remark { get; set; } 12 #region IEntity Members 13 /// <summary> 14 /// 讀取或設定文章的編號 15 /// </summary> 16 public Guid Id { get; set; } 17 #endregion 18 }
文章領域模型對應DTO:
1 public class ArticleDTO 2 { 3 /// <summary> 4 /// 文章唯一編碼 5 /// </summary> 6 public string ArticleID { get; set; } 7 /// <summary> 8 /// 文章標題 9 /// </summary> 10 public string Title { get; set; } 11 /// <summary> 12 /// 文章摘要 13 /// </summary> 14 public string Summary { get; set; } 15 /// <summary> 16 /// 文章內容 17 /// </summary> 18 public string Content { get; set; } 19 /// <summary> 20 /// 文章作者 21 /// </summary> 22 public string Author { get; set; } 23 /// <summary> 24 /// 文章發表日期 25 /// </summary> 26 public DateTime PostTime { get; set; } 27 /// <summary> 28 /// 文章發表年份 29 /// </summary> 30 public int PostYear { get; set; } 31 /// <summary> 32 /// 文章備註 33 /// </summary> 34 public string Remark { get; set; } 35 }
AutoMapper實體轉換
從上面ArticleDTO中可以看到多了兩個屬性:Summary(文章摘要)和PostYear(文章發表年份),這就是我們在特定的業務場景中需要的,並不會影響到領域模型,如何實現領域模型和DTO之間的轉換?我們可以使用AutoMapper可以很方便的對他們進行轉換,一個強大的Object-Object Mapping工具。
工具-庫程式包管理器-程式包管理控制平臺,輸入“Install-Package AutoMapper”命令,就可以把AutoMapper新增到專案中,有關
1 static void Main(string[] args) 2 { 3 Article article = new Article 4 { 5 Title = "漫談實體、物件、DTO及AutoMapper的使用", 6 Content = "實體(Entity)、物件(Object)、DTO(Data Transfer Object)資料傳輸物件,老生常談話題,簡單的概念,換個角度你會發現更多的東西。個人拙見,勿喜請噴。", 7 Author = "xishuai", 8 PostTime = DateTime.Now, 9 Remark = "文章備註" 10 }; 11 //配置AutoMapper 12 AutoMapper.Mapper.Initialize(cfg => 13 { 14 cfg.CreateMap<Article, ArticleDTO>()//建立對映 15 .ForMember(dest => dest.ArticleID, opt => opt.MapFrom(src => src.Id))//指定對映規則 16 .ForMember(dest => dest.Summary, opt => opt.MapFrom(src => src.Content.Substring(0, 10)))//指定對映規則 17 .ForMember(dest => dest.PostYear, opt => opt.MapFrom(src => src.PostTime.Year))//指定對映規則 18 .ForMember(dest => dest.Remark, opt => opt.Ignore());//指定對映規則 忽視沒有的屬性 19 }); 20 21 //呼叫對映 22 ArticleDTO form = AutoMapper.Mapper.Map<Article, ArticleDTO>(article); 23 }
轉換效果:
後記
其實這篇文章原本只是想簡單寫下DTO及AutoMapper的用法,但是不知怎的寫著寫著就寫跑偏了,現在回過頭看也不知道自己寫了些什麼東西,正如張三丰教張無忌太極劍法,問他記得了多少,最後什麼都沒記得。
隨著DDD(領域驅動設計)的學習和對道德經的感悟,就會發覺:此兩者同出而異名,同謂之玄,玄之又玄,眾妙之門。
如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^
參考資料: