注意,【】中是后来加的批注。因为随着对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. 可以安全地暴露业务模型,不用担心业务方法被非法调用。