1. 程式人生 > >【道德經】漫談實體、物件、DTO及AutoMapper的使用

【道德經】漫談實體、物件、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(領域驅動設計)的學習和對道德經的感悟,就會發覺:此兩者同出而異名,同謂之玄,玄之又玄,眾妙之門。

  如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^

  參考資料: