在一个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。
服务中的DTO和MO
在了解了咱们为何须要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主要有如下几部分工做须要完成:
相信读者最早注意到的就是第三点。能够想象到的是,若是将整个系统的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