DTO层的思考

来源:互联网 发布:传奇霸业全套脱机源码 编辑:程序博客网 时间:2024/05/17 23:00

DTO层的思考

 

注意,【】中是后来加的批注。因为随着对DDD的深入了解,对DTO的思考也有所改变。

分布式模式下,DTO层是一定需要的吗?

DTO层的作用是为了隔离Domain Model:让DoMain Model的改动不会直接影响到UI;保持Domain Model的安全,不暴露业务逻辑。

 【最大多数情况看来,UI或者DO的改动,都不可避免地会影响对方,即使中间有DTO隔离,所以这一个理由是不成立的。
至于安全问题,如果不是开放性平台(业务接口的调用者不是安全的),那么也没必要担心业务逻辑会暴露;即使是开放性平台,我们也可以用AOP做权限检查,限制第三者的非法调用。所以安全问题的理由也不成立。】

有两个方案可以省略DTO层,又能起到DTO的作用:

l          继承:定义失血模型的Model,然后再做一个从Model继承的代理类 ,代理类里实现业务逻辑。贫血模型的Model单独为一个DLL,代理模型另起一个DLL。Client端只能引用贫血模型的DLL,这样就达到了隔离的目的,又省略了Contract层。

l          接口:为Domain Model做一个贫血模型的接口,接口单独为一个DLL,Client端只引用接口DLL。

这两种方案的核心思想都是让数据字段与业务方法分离,然后只对Client端公开数据部份。但这种思想会导致域模型趋向事务脚本模型,所以都不可取。

 

综上所述,在使用领域模型的情况下,如果没有DTO层,那么Model是一定会完整地暴露给Client端。

 

暴露Domain Model会带来什么问题?

首先是安全问题。

DoMain Model都带有业务方法,让Client端引用Domain Model就意味着Client端可以绕过Service层直接完成业务逻辑的调用。

 【Client端直接调用Domian Model的方法,甚至直接调用Repository做持久化,在DDD的解决方案中是允许的。至于安全性问题已经在上面反驳过了。】

其次是效率问题。

Domain Model通常很“厚”(Model与Model嵌套得很深),在广域网上传输大对象会有严重的效率问题。

 

再有就是跨平台的问题。

Domain Model都是与特定的语言的数据类型有关,而这些数据类型是不能跨平台的,比如Java的类型就不能被C#使用。但在分布式模式下,Client端与Server端的平台不同是很正常的,如果Service直接返回Domain Model,Client端根本无法解析,这就要求Service返回的结果必须是标准的格式字节流。

让Domain Model只使用简单类型(字符和数值)?

让数据类型约束Domain Model显然不是一个好想法,所以DTO似乎是必不可少的了。

 【是的,跨平台是DTO唯一存在的价值,JSON和XML大行其道并不是没有道理的】

如果我们一定要抛弃DTO层呢?

我们花这么大力气想省略DTO,就是因为这玩意太麻烦了,而且它的作用如此之小,代价却如此之大。

 

如果我们的系统只运行在局域网,网络都是光纤,我们有着强大的服务器,而且我们的开发人员严格地遵守Domain Model调用规则,只使用数据字段,不调用业务方法,并且我们的系统使用同一种语言开发,绝对不会跨平台……

 

我们把一切不利于暴露Domain Model的弱点都屏蔽之后,是不是意味着我们可以省略DTO层了?

嗯,看起来似乎是可以省略了。但前台开发人员真的能严格遵守调用规则吗?估计这时候,前台开发人员会进而质疑Service层的存在意义了……

 【是的,在DDD中Client端是允许绕过Service,直接访问Domain Model的业务方法,以及Repository层,所以简单模块(只有crud,不用跨领域模块的功能)根本没有Service类。】

贫血模型 + 事务脚本模式

贫血模型和事务脚本模式都是与领域模型对立的,领域模型主张充血模型。

 

便贫血模型+事务脚本模式也有好处:

1.          开发简单。大多数的开发人员都有用过这种模式——以PetShop为代表的ADO.NET分层构架(MOD,DAL,BL)。

2.          可以安全地暴露业务模型,不用担心业务方法被非法调用。


分布式系统(distributed system):是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。

分布式软件系统(Distributed Software Systems)是支持分布式处理的软件系统,是在由通信网络互联的多处理机体系结构上执行任务的系统。它包括分布式操作系统、分布式程序设计语言及其编译(解释)系统、分布式文件系统和分布式数据库系统等。


(以下资料没整理)

层间数据传输的过程就是服务的执行者将数据返回给服务的调用者的过程。

层间数据传输假定的场景是“服务器将执行的数据结果如何传递给远程客户端”

在分布式系统中,客户端和服务器端交互有两种情形:第一个是客户端从服务器端读取数据;第二个是客户端将本身的数据传递给服务器端。

DTO(Data Transfer Object,数据传送对象)是解决这个问题的比较好的方式。DTO是一个普通的Java类,它封装了要传送的批量的数据。当客户端需要读取服务器端的数据的时候,服务器端将数据封装在DTO中,这样客户端就可以在一个网络调用中获得它需要的所有数据。

使用DTO的时候,一个主要问题是选择什么样的DTO:这个DTO能够容纳哪些数据,DTO的结构是什么,这个DTO是如何产生的。

DTO是服务器端和客户端进行通信的一个协议格式,合理的DTO设计将会使得服务器和客户端的通信更加顺畅。

域模型是指从业务模型中抽取出来的对象模型,比如商品、仓库。在J2EE中,最常见的域模型就是可持久化对象,比如Hibernate中的PO、EJB中的实体Bean。

在分布式系统中,域模型完全位于服务器端。根据持久化对象可否直接传递到客户端,域对象可以分为两种类型:一种是服务器端的持久化对象不可以直接传递到客户端,比如EJB中的实体Bean是不能被传递到客户端的;一种是持久化对象可以直接传递到客户端,比如Hibernate中的PO变为detached object以后就可以传递到客户端。


使用域DTO会有如下好处:

域模型结构可以在一次网络调用中复制到客户端,客户端可以读取、更新这个DTO而不需要额外的网络调用开销,而且客户端还可以通过将更新后的DTO回传到服务器端以更新数据

易于实现快速开发。通过使用域DTO可以直接将域模型在层间传输,减少了工作量,可以快速地构建出一个应用。

但它也有如下的缺点:

将客户端和服务器端域对象耦合在一起。如果域模型变了,那么相应的DTO也会改变,即使对于Hibernate这种PO、DTO一体的系统来说也会同样导致客户端的代码要重新编译或者修改。

l 不能很好地满足客户端的要求。客户端可能只需要域对象的20个属性中的一两个,采用域DTO则会将20个属性都传递到客户端,浪费了网络资源。

l更新域对象很烦琐。客户端对DTO可能做了很多更新或者很深层次的更新,要探查这些更新然后更新域对象是很麻烦的事情。

域DTO解决了在客户端和服务器端之间传递大量数据的问题,但是客户端往往需要更细粒度的数据访问。

可定制的DTO,使它仅封装客户端需要的数据的任意组合,完全与服务器端的域模型相分离。定制DTO与域DTO的区别就是它不映射到任何服务器端的域模型。

定制DTO主要用于只读操作,也就是DTO只能用来显示,而不能接受改变。既然定制DTO对象仅仅是一个数据的集合,和任何服务端对象没有必然的关系,那么对定制DTO进行更新就是没有意义的了。

定制DTO的缺点如下:

l   需要创建大量的DTO。使用定制DTO会爆炸式地产生大量的对象。

l   客户端DTO的版本必须和服务器端的版本一致。由于客户端和服务器端都通过定制DTO通信,所以一旦服务器端的DTO增加了字段,那么客户端的代码也必须重新编译,否则会产生类版本不一致的问题。


数据访问模式:

如果说数据访问技术是一些得心应手的兵器的话,数据访问模式就应该是各种武功秘笈了,它们才是致胜的法宝。 架构师在不同的应用场合下可能会选择不同的数据访问模式,并且还会不断地推陈出新,这里不会也不可能穷尽所有的数据访问模式,而只是列举了其中最为典型的几个。

在线访问


Data Access Object(DAO)


一个典型的 DAO 实现通常有以下组件:

一个 DAO 工厂类

一个 DAO 接口

一个实现了 DAO 接口的具体类 数据传输对象(有时称为值对象) 这当中具体的 DAO 类包含访问特定数据源的数据的逻辑。

DTO模式


Data Transfer Object 是经典 EJB 设计模式之一。 DTO 本身是这样一组对象或是数据的容器,它需要跨不同的进程或是网络的边界来传输数据。这类对象本身应该不包含具体的业务逻辑,并且通常这些对象内部只能进行一些诸如内部一致性检查和基本验证之类的方法,而且这些方法最好不要再调用其它的对象行为。

1.使用编程语言内置的集合对象,它通常只需要一个类,就可以在整个应用程序中满足任何数据传输目的;而且因为几乎所有的编程语言都内置了集合类型,我们不需要再另外编写这个类的实现代码。同时,使用内置的集合对象来实现 DTO 对象的时候,客户端必须按位置序号(在简单数组的情况下)或元素名称(在键控集合的情况下)访问集合内的字段。此外,集合存储的是同一类型(通常是最基本的 Object 类型)的对象,这有时会导致在编译时碰到一些无法检测到编码错误,这是采用内置集合对象来实现 DTO 对象的缺点。


2.通过创建自定义类来实现 DTO 对象,通过定义显式的 get 或是 set 方法来访问数据。这种方式的主要优点在于能够提供与任何其他对象完全一样的、客户端应用程序可访问的强类型对象,这种对象可以提供编译时的类型检查;但是主要缺点在于增加了编码的工作量,因为如果应用程序发出许多远程调用的话,我们需要自己编写大量的调用代码。


具体实现中有许多方法试图将上述这两种方法的优点结合在一起。第一种方法是代码生成技术,该技术可以生成脱离现有元数据(如可扩展标记语言 ( XML ) 架构)的自定义 DTO 类的源代码;第二种方法是提供更强大的集合,尽管它也是平台内置的一般的集合,但它将关系和数据类型信息与原始数据存储在一起,比如 IBM 提出的 SDO 技术或是微软 ADO.NET 中的 DataSet 就支持这类方法。


无论采用上述的哪种方法,当有了 DTO 对象之后,就需要用数据来填充它。在大多数情况下,DTO 内部填充的数据往往来自于多个其它种类的对象;由于 DTO 对象中很少有具体的数据操作方法,因此它很难从其它对象中直接提取数据。这种设计是有道理的,因为如果不让 DTO 对象知道如何调用其它对象的方法,我们就可以在不同的场合直接重用 DTO 对象,这样一旦其它对象发生更改,我们无需修改 DTO 对象的设计。

原创粉丝点击