上一篇文章做爲一個引子,說明了領域驅動設計的優點,從本篇文章開始,筆者將會結合本身的實際經驗,談及領域驅動設計的應用。本篇文章主要討論一下咱們常常會用到的一些對象:VO、DTO、DO和PO。 html
因爲不一樣的項目和開發人員有不一樣的命名習慣,這裏我首先對上述的概念進行一個簡單描述,名字只是個標識,咱們重點關注其概念: java
概念: 程序員
VO(View Object):視圖對象,用於展現層,它的做用是把某個指定頁面(或組件)的全部數據封裝起來。 數據庫
DTO(Data Transfer Object):數據傳輸對象,這個概念來源於J2EE的設計模式,原來的目的是爲了EJB的分佈式應用提供粗粒度的數據實體,以減小分佈式調用的次數,從而提升分佈式調用的性能和下降網絡負載,但在這裏,我泛指用於展現層與服務層之間的數據傳輸對象。 設計模式
DO(Domain Object):領域對象,就是從現實世界中抽象出來的有形或無形的業務實體。 網絡
PO(Persistent Object):持久化對象,它跟持久層(一般是關係型數據庫)的數據結構造成一一對應的映射關係,若是持久層是關係型數據庫,那麼,數據表中的每一個字段(或若干個)就對應PO的一個(或若干個)屬性。 session
模型: 數據結構
下面以一個時序圖創建簡單模型來描述上述對象在三層架構應用中的位置: 架構
VO與DTO的區別 框架
你們可能會有個疑問(在筆者參與的項目中,不少程序員也有相同的疑惑):既然DTO是展現層與服務層之間傳遞數據的對象,爲何還須要一個VO呢?對!對於絕大部分的應用場景來講,DTO和VO的屬性值基本是一致的,並且他們一般都是POJO,所以不必畫蛇添足。但不要忘記這是實現層面的思惟,對於設計層面來講,概念上仍是應該存在VO和DTO,由於二者有着本質的區別,DTO表明服務層須要接收的數據和返回的數據,而VO表明展現層須要顯示的數據。
用一個例子來講明可能會比較容易理解:例如服務層有一個getUser的方法返回一個系統用戶,其中有一個屬性是gender(性別),對於服務層來講,它只從語義上定義:1-男性,2-女性,0-未指定,而對於展現層來講,它可能須要用「帥哥」表明男性,用「美女」表明女性,用「祕密」表明未指定。說到這裏,可能你還會反駁,在服務層直接就返回「帥哥美女」不就好了嗎?對於大部分應用來講,這不是問題,但設想一下,若是需求容許客戶能夠定製風格,而不一樣風格對於「性別」的表現方式不同,又或者這個服務同時供多個客戶端使用(不一樣門戶),而不一樣的客戶端對於表現層的要求有所不一樣,那麼,問題就來了。再者,回到設計層面上分析,從職責單一原則來看,服務層只負責業務,與具體的表現形式無關,所以,它返回的DTO,不該該出現與表現形式的耦合。
理論歸理論,這到底仍是分析設計層面的思惟,是否在實現層面必須這樣作呢?一刀切的作法每每會得不償失,下面我立刻會分析應用中如何作出正確的選擇。
VO與DTO的應用
上面只是用了一個簡單的例子來講明VO與DTO在概念上的區別,本節將會告訴你如何在應用中作出正確的選擇。
在如下才場景中,咱們能夠考慮把VO與DTO二合爲一(注意:是實現層面):
如下場景須要優先考慮VO、DTO並存:
DTO與DO的區別
首先是概念上的區別,DTO是展現層和服務層之間的數據傳輸對象(能夠認爲是二者之間的協議),而DO是對現實世界各類業務角色的抽象,這就引出了二者在數據上的區別,例如UserInfo和User(對於DTO和DO的命名規則,請參見筆者前面的一篇博文),對於一個getUser方法來講,本質上它永遠不該該返回用戶的密碼,所以UserInfo至少比User少一個password的數據。而在領域驅動設計中,正如第一篇系列文章所說,DO不是簡單的POJO,它具備領域業務邏輯。
DTO與DO的應用
從上一節的例子中,細心的讀者可能會發現問題:既然getUser方法返回的UserInfo不該該包含password,那麼就不該該存在password這個屬性定義,但若是同時有一個createUser的方法,傳入的UserInfo須要包含用戶的password,怎麼辦?在設計層面,展現層向服務層傳遞的DTO與服務層返回給展現層的DTO在概念上是不一樣的,但在實現層面,咱們一般不多會這樣作(定義兩個UserInfo,甚至更多),由於這樣作並不見得很明智,咱們徹底能夠設計一個徹底兼容的DTO,在服務層接收數據的時候,不應由展現層設置的屬性(如訂單的總價應該由其單價、數量、折扣等決定),不管展現層是否設置,服務層都一律忽略,而在服務層返回數據時,不應返回的數據(如用戶密碼),就不設置對應的屬性。
對於DO來講,還有一點須要說明:爲何不在服務層中直接返回DO呢?這樣能夠省去DTO的編碼和轉換工做,緣由以下:
對於DTO來講,也有一點必須進行說明,就是DTO應該是一個「扁平的二維對象」,舉個例子來講明:若是User會關聯若干個其餘實體(例如Address、Account、Region等),那麼getUser()返回的UserInfo,是否就須要把其關聯的對象的DTO都一併返回呢?若是這樣的話,必然致使數據傳輸量的大增,對於分佈式應用來講,因爲涉及數據在網絡上的傳輸、序列化和反序列化,這種設計更不可接受。若是getUser除了要返回User的基本信息外,還須要返回一個AccountId、AccountName、RegionId、RegionName,那麼,請把這些屬性定義到UserInfo中,把一個「立體」的對象樹「壓扁」成一個「扁平的二維對象」。筆者目前參與的項目是一個分佈式系統,該系統無論三七二十一,把一個對象的全部關聯對象都轉換爲相同結構的DTO對象樹並返回,致使性能很是的慢。
DO與PO的區別
DO和PO在絕大部分狀況下是一一對應的,PO是隻含有get/set方法的POJO,但某些場景仍是能反映出二者在概念上存在本質的區別:
DO與PO的應用
因爲ORM框架的功能很是強大而大行其道,並且JavaEE也推出了JPA規範,如今的業務應用開發,基本上不須要區分DO與PO,PO徹底能夠經過JPA,Hibernate Annotations/hbm隱藏在DO之中。雖然如此,但有些問題咱們還必須注意:
到目前爲止,相信你們都已經比較清晰的瞭解VO、DTO、DO、PO的概念、區別和實際應用了。經過上面的詳細分析,咱們還能夠總結出一個原則:分析設計層面和實現層面徹底是兩個獨立的層面,即便實現層面經過某種技術手段能夠把兩個徹底獨立的概念合二爲一,在分析設計層面,咱們仍然(至少在頭腦中)須要把概念上獨立的東西清晰的區分開來,這個原則對於作好分析設計很是重要(工具越先進,每每會讓咱們越麻木)。第一篇系列博文拋磚引玉,大唱領域驅動設計的優點,但其實領域驅動設計在現實環境中仍是有種種的限制,須要選擇性的使用,正如我在《田七的智慧》博文中提到,咱們不能永遠的理想化的去選擇所謂「最好的設計」,在必要的狀況下,咱們仍是要勇於放棄,由於最合適的設計纔是最好的設計。原本,系列中的第二篇博文應該是討論領取驅動設計的限制和如何選擇性的使用,但請原諒個人疏忽,下一篇系列博文會把這個主題補上,敬請關注。