DTO – 服務實現中的核心數據

  在一個Web服務的實現中,咱們經常須要訪問數據庫,並將從數據庫中所取得的數據顯示在用戶頁面中。這樣作的一個問題是:用於在用戶頁面上展現的數據和從數據庫中取得的數據經常具備較大區別。在這種狀況下,咱們經常須要向服務端發送多個請求才能將用於在頁面中展現的數據湊齊。html

  一個解決該問題的方法就是根據不一樣需求使用不一樣的數據表現形式。在一個服務實現中較爲常見的數據表現形式有MO(Model Object,在有些上下文中也被稱爲VO,Value Object)和DTO(Data Transfer Object)。MO用來表示從數據庫中讀取的數據,而DTO則用來表示在網絡上所傳輸的數據。數據庫

  在本文中,咱們將討論如何在一個Web服務的實現中使用DTO及MO,並會對其它一些相關數據表現形式,如View Model等進行簡單地介紹。瀏覽器

 

Why DTO?服務器

  不管是桌面應用仍是Web服務,其內部的數據表現都是很是重要的。在一個初學者瞭解一個系統的時候,其首先須要瞭解整個系統中的各個組件的做用,而後再瞭解系統中的Workflow,即在執行業務邏輯時各個組件是如何協同工做的。在瞭解了這兩部分以後,該初學者須要作的事情就是詳細地梳理一遍數據是如何在整個系統中流動的,便是整理並理解數據流(Dataflow)的過程。而在真正理解了數據流後,該初學者才具備了在系統中開發的能力。網絡

  整理數據流的過程是一個逐步細化的過程:從鑑別數據結構到該數據結構中的每一個屬性究竟是如何使用的。在整個數據流中,任何一個屬性值的改變均可能會致使數據的處理方式發生變化。數據結構

  在整理數據流的時候咱們要作什麼樣的事情呢?首先咱們須要鑑別出到底哪些數據會在各個組件之間進行傳送,在傳送過程當中進行了什麼樣的轉化,這些數據是如何構建出來的,又由它構建了哪些數據,最終這些數據是否被持久化到了本地存儲中等等。架構

  而在整理數據流的過程當中,數據的轉化經常是最難理解的部分。一個數據類型的定義經常與其運行環境有關。例如在一個電子商務網站中,一個表示商品的類Product可能包含了該商品的全部信息:商品的名稱,品牌,詳細介紹,價格等。在用戶使用電腦瀏覽器瀏覽的時候,這些信息都將被顯示在頁面上。可是在用戶使用手機進行瀏覽時,咱們就須要考慮如何爲這些手機用戶節省流量的問題。一種節省用戶手機流量的方法就是首先顯示商品的簡略信息,並在用戶決定查看商品的詳細介紹時再從服務端下載商品的詳細信息。在這種狀況下,包含商品全部信息的類Product將再也不是適合傳輸的數據結構。app

  而問題不只僅出在須要將數據結構拆分的狀況下,更可能出如今數據合併的狀況中。例如網頁的UE爲了提升用戶體驗,要求在產品頁面中直接將該商品品牌的詳細信息顯示在頁面中。在這種狀況下,咱們就須要在表示商品的類Product中添加一個記錄該商品品牌的域brand。可是在數據庫中,表示商品的類Product可能僅僅記錄了商品品牌的ID。所以在業務邏輯中,咱們就須要將Product和其對應的Brand合併在一塊兒。函數

  甚至說,咱們能夠將事情弄得更復雜一些:工具

  在上面的圖中,咱們展現了數據在一個系統中可能存在的多種不一樣表現形式。在圖片的中央位置的是一個服務器,多種客戶端都將從它那裏得到產品信息。就像前面所說,爲了節省客戶端的流量,服務端向移動客戶端所發送的數據將是產品信息在服務端中的簡略版本。而在一個瀏覽器訪問該產品的時候,表示商品品牌的信息將內嵌在產品信息之中,以提供更好的用戶體驗。除了與客戶端通信,服務端之間也可能產生信息的交換。在該交換過程當中,表示產品的Product以及表示品牌的Brand則彼此獨立地在服務端之間傳遞。而就一個運行在遠端的Agent而言,其可能僅僅須要一個Product的ID來監控產品在生產製做方面的狀態。

  而這一切數據都應當從系統的數據庫中獲得。數據庫中的數據不可能同時存儲並維護這一系列數據結構,所以在一個複雜的系統中,數據庫中的數據表示與系統中所傳輸的數據之間經常是不一樣的數據結構。常見的狀況則是將其分爲兩類:一類用來訪問數據庫,在系統中表現數據庫中所記錄的數據,叫MO,即Model Object;另外一類用來在網絡中傳輸,叫DTO,即Data Transfer Object。

 

服務中的DTOMO

  在瞭解了咱們爲何須要DTO和MO等數據的不一樣表示以後,就讓咱們來看看這些數據表示在一個Web服務中是如何工做的。

  先讓咱們從最簡單的Web服務分層開始提及。一個最簡單的Web服務主要分爲數據訪問層(DAL),業務邏輯層以及表現層三個部分。其中表現層是運行在客戶端的,而其它兩個層次則運行在服務端。當數據從DAL層讀取出來的時候,其所記錄的數據與數據庫中所記錄的數據是一致的,所以它們就是咱們這篇文章中討論的MO。而在傳輸給客戶端的時候,這些數據可能會和MO不一樣,所以其爲DTO:

  如今就讓咱們放大一下數據訪問層,來看看數據訪問層中MO所在的位置:

  首先要強調的是,實現數據訪問層的方式有不少種,而上圖所展現的僅僅是一種基於Repository模式的實現。經過Repository來實現DAL是一種最爲常見的數據訪問層實現方式。就像上圖所展現的那樣,在一個基於Repository模式的實現中,數據訪問層將擁有一系列Repository實例。這些Repository實例依賴於系統所使用的ORM來將數據庫中的數據轉化爲Java類實例。這些Java類實例實際上就是在該數據訪問層所提供給業務邏輯層的MO。

  而DTO則用於在服務與客戶之間以及服務和服務之間進行數據的傳遞。在這些傳遞過程當中,對DTO的需求多是多種多樣的:

  上面的圖片展現了一段Product這種類型的DTO在服務端和客戶端以及服務端之間進行交互的過程。在該流程中,所須要傳遞的DTO並不相同:在使用瀏覽器讀取和保存有關Product的信息時,二者的數據表現形式可能會有一些細微的差異。而在保存完畢後,服務可能會將新的Product做爲負載來向其它服務器發送請求,而此時所使用的Product的表示又可能與前兩種略有差異。若是爲這些細微的差異定義不少不一樣的DTO,那麼系統對數據的管理可能會遇到一系列麻煩。例如在一個複雜的系統中,DTO可能會按照下面的方式在系統中流轉:

  在上圖中,咱們展現了一個DTO在依次流轉過多個服務的狀況。若是在DTO依次傳遞的過程當中使用了不一樣的DTO表示,那麼一個服務所須要的DTO可能和另外一個服務中所擁有的DTO並不匹配。這即是DTO反過來會影響到架構設計的一個最簡單的例子,卻也是DTO管理中最多見的問題,那就是DTO的表現形式過多。若是爲全部的不一樣需求都建立一個DTO,那麼一個概念所對應的DTO可能多達5,6種,很是難於管理。這種管理上的困難經常存在於如何指定某個服務所須要使用的DTO種類,以及在更改DTO時須要同時修改一系列DTO的狀況中。

  爲了防止DTO因爲不一樣的需求而衍生出過多的種類,服務實現中經常容許DTO中的數據包含一些冗餘。

 

逐步添加你的DTO

  那麼咱們該如何向系統中添加DTO呢?答案是,根據狀況決定。在項目的一開始,數據庫中所存儲的數據與頁面所須要顯示的數據經常是一致的,所以在這種狀況下,咱們並不須要DTO的幫助。而在所須要的數據和數據庫所記錄的數據再也不同樣的時候,咱們就須要考慮是否須要在項目中添加DTO了。這時軟件開發人員就須要問本身:這種所須要的數據與數據庫中的數據不一致的狀況是否經常出現?若是答案是「是」,那麼咱們就須要開始着手準備添加對DTO的支持。

  在系統中添加DTO主要有如下幾部分工做須要完成:

  1. 添加DTO類。
  2. 添加從MO到DTO的轉化邏輯。
  3. 將本來對MO的使用轉換爲對DTO的使用。

  相信讀者最早注意到的就是第三點。能夠想象到的是,若是將整個系統的MO替換成DTO,那麼它的影響面將會很是大,並且很是容易出錯。所以在一個大型項目中,咱們經常須要預先判斷DTO的必要性,進而儘早地添加DTO。

  讓咱們回過頭來看看第一個任務應該如何完成。在一個系統中,DTO經常用來傳輸數據,所以其自身每每不帶有任何邏輯。這也即是這些DTO經常被定義成JavaBean的緣由。以JavaBean的形式來定義DTO帶來了一個巨大的好處,那就是不少第三方類庫都提供了生成JavaBean的功能。在這種狀況下,軟件開發人員只須要經過一系列描述性語言來描述這些DTO便可。這其中最經常使用的即是JAXB。

  在使用JAXB時,軟件開發人員只須要在.xsd文件中編寫一系列描述性信息:

1 <xsd:complexType name="Address">
2   <xsd:sequence>
3     <xsd:element name="name" type="xsd:string"/>
4     <xsd:element name="street" type="xsd:string"/>
5     <xsd:element name="city" type="xsd:string"/>
6     <xsd:element name="state" type="xsd:string"/>
7   </xsd:sequence>
8   <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US"/>
9 </xsd:complexType>

  那麼在JAXB運行完畢後,相應的Java類型就將被生成:

 1 @XmlAccessorType(AccessType.FIELD)
 2 @XmlType(name = "Address", propOrder = {
 3     "name",
 4     "street",
 5     "city",
 6     "state"
 7 })
 8 public class Address {
 9     protected String name;
10     protected String street;
11     ……
12     @XmlAttribute
13     @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
14     protected String country;
15 
16     public String getName() {
17         return name;
18     }
19 
20     public void setName(String value) {
21         this.name = value;
22     }
23 
24     public String getStreet() {
25         return street;
26     }
27 
28     public void setStreet(String value) {
29         this.street = value;
30     }
31     ……
32     public String getCountry() {
33         if (country == null) {
34             return "US";
35         } else {
36             return country;
37         }
38     }
39 
40     public void setCountry(String value) {
41         this.country = value;
42     }
43 }

  是否是很簡單?在知道了如何建立一個DTO以後,咱們就須要考慮如何將MO轉化成爲DTO。固然,這依然有第三方工具能夠幫助咱們完成這個事情。一個較爲著名的工具就是Dozer。使用Dozer也很簡單,在它的配置文件裏面標明須要相互轉換的兩個類型便可:

1 <mapping> 
2     <class-a>com.ambergarden.egoods.mo.Address</class-a>
3     <class-b>com.ambergarden.edoods.dto.Address</class-b>
4 </mapping>

  在運行時,Dozer會使用反射來對這兩個類型中的各個同名屬性進行匹配並賦值。若是兩個類型中擁有不一樣名的屬性,那麼軟件開發人員能夠顯式地指定相互匹配的屬性:

1 <mapping> 
2     <class-a>com.ambergarden.egoods.mo.Address</class-a>
3     <class-b>com.ambergarden.edoods.dto.Address</class-b>   
4     <field>
5         <a>name</a>
6         <b>owner</b>
7     </field>
8 </mapping>

  除此以外,Dozer還支持很是多的轉換功能,在這裏咱們便不一一進行介紹了。

  在有這些工具的輔助下,爲系統添加DTO已經變得簡單多了。在對DTO的平常維護中,咱們可能須要添加一些新的DTO,或者更改已有的DTO。在這種狀況下,咱們只須要更改對DTO進行描述的文件並更新Dozer的配置文件便可。固然,若是在Dozer中使用了自定義轉換邏輯,那麼軟件開發人員還須要更新相應的轉換邏輯。

 

貧血的DTO

  DTO中只包含數據,並無包含任何行爲。「這我知道」,或許你會說。

  可是千萬不要大意。這經常會致使你陷入貧血模型的陷阱中。在服務端的業務邏輯實現以及客戶端的頁面邏輯中,咱們有時須要指定對這些數據的操做邏輯。從面向對象設計的角度來講,某些邏輯實際上就應該定義在這些類型中。可是因爲DTO自己沒有定義這些邏輯,所以咱們須要在這些類型以外定義它們,例如在一個Helper類中爲這些類型定義一系列輔助函數。

  一個最簡單的示例就是對數據有效性的檢查。例如在一個Person類中,咱們使用一個整型數據記錄了該人物的年齡:

1 class Person {
2     private int age;
3     ……
4 }

  那麼在業務邏輯中,咱們就須要檢查該域是否被設置爲負數。因爲DTO是使用工具自動生成的,所以這些檢查邏輯沒法放在該DTO類中。做爲一種變通方式,咱們須要寫一個輔助類來完成該功能。但隨着這種需求愈來愈多,對這些輔助功能的管理將愈來愈困難。此時你就將徹底陷入到貧血模型的陷阱中。

  也就是說,DTO的主要職責是爲了傳輸數據,但它並不擅長,甚至是不適合在業務邏輯中表示一個複雜概念。一個複雜概念經常與一些可重用的複雜邏輯關聯,但這正是DTO所不能辦到的。

  爲了解決這個問題,咱們能夠在服務端添加一個業務邏輯表現,即BO(Business Object)。在這種狀況下,MO將不會直接轉化爲DTO,而是轉化爲BO。在全部業務處理完畢並須要將數據發送給客戶的時候,BO將轉化爲DTO以進行傳輸。

  而在客戶端,咱們一樣能夠引入一層新的更適合於頁面邏輯的數據表現。這種數據表現被稱爲VM(ViewModel),即爲了表觀展現所定義的模型。有時候,有些類庫提供了更爲簡單的方法,例如YUI和ExtJS所提供的Mixin功能。

  固然,在添加這些數據展示形式以前,軟件開發人員須要仔細考量添加這些模型所須要的工做量和所帶來效益之間的平衡。

 

DTO vs. DAO

  有些人看到這個標題時可能會一愣。是的,二者並無任何可比性。可是若是一我的瞭解了DTO,並知道了DAO是Data Access Object的縮寫,那麼他可能會很天然地認爲DAO與DTO相似,是用來表示從DAL所取得的用來表示數據庫中數據的類型。

  可實際上,DAO則是一種組織數據庫訪問邏輯的一種標準模式。也就是說,與其對應的應該是Repository模式等一系列數據訪問的經常使用方法。所以在本文的最後,咱們須要着重強調DAO和MO並非一個概念。而因爲本文主要着重介紹數據,而且DAO自己也能夠做爲一篇獨立的博客,所以在本文中將再也不對其進行詳細地介紹。

 

Copyright:

轉載請註明原文地址並標明轉載:http://www.cnblogs.com/loveis715/p/4379656.html

 

商業轉載請事先與我聯繫:silverfox715@sina.com

相關文章
相關標籤/搜索