本文将介绍DDD分层架构中普遍使用的数据传输对象Dto,而且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行比较。前端
当你阅读我或其它博主提供的示例代码时,会发现几种类型的实体,这几种实体初步看上去区别不大,只是名称不一样,特别在这些示例很是简单的状况下更是如此。你可能会疑惑为什么要搞得这么复杂,采用一种实体不是更好?程序员
在最理想的状况下,咱们只想采用领域实体Entity进行全部的操做。ajax
领域实体是领域层的核心,是业务逻辑的主要放置场所。换句话说,领域实体中包含了大量业务逻辑方法。json
若是领域实体中的属性都包含getter和setter,而且全部属性都是public的,那么,使用这个Entity的程序员可能会绕过业务方法,直接操做属性进行赋值。网络
为属性直接赋值,是面向数据的过程式思惟,而调用方法是面向对象的方式,这也是领域模型的核心所在。架构
因此为了强制实施业务规则,必须把业务方法操做过的属性的setter访问器隐藏起来,不然这个方法不会有人调用。app
当领域实体某些属性的setter被隐藏后,直接在表现层操做领域实体将变得困难,由于Mvc或Wpf的模型绑定只能操做public的属性。框架
哪怕你的系统没有使用分布式,好比只是一个Mvc网站,但因为前端要求愈来愈高,客户端不少时候须要经过ajax与服务端进行交流,通常采用json格式传递数据,这就要求你的实体可以序列化。分布式
对领域实体进行序列化,首先须要考虑的问题是,可能序列化一个较大的对象图,从而致使没必要要的开销。ide
领域实体通常包含导航属性指向其它领域实体,其它的领域实体可能包含更多导航属性,从而组成一个对象图。若是采用Serializable特性进行序列化,而且没有指定其它序列化选项,可能致使把一个庞大的对象图序列化并进行网络传输。
另外一个问题是,复杂的领域实体可能包含循环引用,从而致使序列化失败。
对于序列化,一个更好的选择是采用DataContract特性,被DataContract修饰过的类成员,不会被自动序列化,必须在成员上明确指定DataMember特性。
DataMember在必定程度上能够缓解上述问题,好比减小须要序列化的数据,不序列化循环引用的对象等,但没法从根本上解决问题。
对于不一样的客户端,可能须要的数据和格式不一样,这属于应用层需求,而领域实体只有一个,在领域实体上经过标记DataMember进行序列化费力不讨好,没法知足复杂的应用需求。
哪怕你只有一个Mvc网站,若是页面上须要显示一些领域实体不存在的数据,你根据这个需求,直接在领域实体上增长属性是很是糟糕的作法,会严重污染你的领域模型,将大大下降领域实体的复用能力。
从以上能够看出,对于一个比较复杂的系统,单凭领域实体很难完成任务,将太多的职责强加到领域实体上,会致使领域实体严重变形。
数据传输对象,即Data Transfer Object,简称DTO。
一个为了减小方法调用次数而在进程间传输数据的对象,《企业应用架构模式》如是说。
能够看出,DTO用于分布式环境,主要用来解决分布式调用的性能问题。同一进程内的对象调用,速度是很是快的,但跨进程调用,甚至跨网络调用,性能降低N个数量级。为了提高性能,须要减小调用次数,这就要求把屡次调用的结果打包成一个对象,在一次调用中返回尽可能多的数据。
上面是DTO的原始含义,下面来看看个人山寨用法。
虽然我也取名为DTO,但个人动机并不彻底是一次打包更多数据来提高性能,而是解决上面提到的几个问题,固然它们之间有必定关系,能够看做一种变种用法。
DTO是一个贫血对象,也就是它里面基本没有方法,只有一堆属性,而且全部属性都具备public的getter和setter访问器。
DTO拥有public的setter访问器,方便的解决了表现层的模型绑定问题。
因为DTO不执行业务操做,仅用于传递数据,因此不该该定义很是复杂的对象引用关系,这样就避免了循环引用,解决了对象序列化的问题。
DTO能够根据应用需求定义成不一样的粒度,在通常状况下,DTO是聚合粒度,也就是说,一个领域层的聚合对应一个DTO,这样作的一个好处是方便对CRUD操做进行抽象以及代码生成。
界面若是想保持简单,应该尽可能一个界面操做一个聚合,将聚合的数据映射到DTO后,传给视图展现。
对于更加复杂的界面,须要在一个界面操做多个聚合,这种状况下,把须要的所有数据打包到DTO进行操做。
从以上介绍中,你应该了解DTO不能理解为单表操做,它能够包含你须要的所有数据。
DTO处于应用层,在表现层与领域层之间传递数据。
DTO由应用层服务使用,应用层服务从仓储中得到聚合,并调用DTO转换器将聚合映射为DTO,再将DTO传递给表现层。
关于应用层服务,后续再专门介绍。
聚合与DTO的转换,看上去是一个简单问题,在聚合与DTO几乎彻底一致的状况下,采用映射组件将很是省力。不少人采用AutoMapper,但它的性能稍微差了点,EmitMapper是更好的选择,性能接近硬编码。
当DTO与聚合显著不一样时,我发现手工编码更加清晰高效。我采用代码生成器建立出一个代码基础,在有个性化需求时,手工修改映射代码。
我老是采用一个静态类来扩展DTO和聚合,为它们添加相关的转换方法。
using Biz.Security.Domains.Models; using Util; namespace Biz.Security.Services.Dtos { /// <summary>
/// 应用程序数据传输对象扩展 /// </summary>
public static class ApplicationDtoExtension { /// <summary>
/// 转换为应用程序实体 /// </summary>
/// <param name="dto">应用程序数据传输对象</param>
public static Application ToEntity( this ApplicationDto dto ) { return new Application( dto.Id.ToGuid() ) { Code = dto.Code, Name = dto.Name, Note = dto.Note, Enabled = dto.Enabled, CreateTime = dto.CreateTime, Version = dto.Version, }; } /// <summary>
/// 转换为应用程序数据传输对象 /// </summary>
/// <param name="entity">应用程序实体</param>
public static ApplicationDto ToDto( this Application entity ) { return new ApplicationDto { Id = entity.Id.ToString(), Code = entity.Code, Name = entity.Name, Note = entity.Note, Enabled = entity.Enabled, CreateTime = entity.CreateTime, Version = entity.Version, }; } } }
ViewModel是为特定视图专门定义的实体对象,专为该视图服务。
对于WPF,ViewModel是必须的,用来支持MVVM模式进行双向绑定。
那么MVC呢,必定须要它吗?
因为采用了DTO,在通常状况下,我都把这个DTO看成ViewModel来使用。若是界面上须要某个属性,我会直接添加到DTO上。
一个例外是,若是MVC的界面很是复杂,我感受把大量的垃圾属性加到DTO上不合适,就会建立专门的ViewModel。
查询实体这个说法,是我乱取的,估计你在其它地方也没有据说过。使用它的缘由,是用来配合个人查询组件一块儿工做。
我前面已经介绍过查询相关的内容,核心思想是经过判断一个可空属性,自动完成空值判断,这是一个强大的特性,帮助你免于编写大量杂乱无章的判断。
查询实体的基本特征就是全部属性必须可空,而且它足够简单,不会拥有集合那样的子对象,全部属性都是扁平化的。
经过传递查询实体,表现层能够作到尽可能简单,因为表现层支持模型绑定,甚至不须要代码,省力是我搭建框架的一个基本出发点。
固然查询实体只支持简单查询,不支持灵活的动态查询,好比让客户设置查询运算符等,暂时没有这方面的需求,若是后续有需求,会扩展一个出来。
查询实体示例:
using System.ComponentModel.DataAnnotations; using Util; using Util.Domains.Repositories; namespace Biz.Security.Domains.Queries { /// <summary>
/// 应用程序查询实体 /// </summary>
public class ApplicationQuery : Pager { /// <summary>
/// 应用程序编号 /// </summary>
[Display( Name = "应用程序编号" )] public System.Guid? ApplicationId { get; set; } private string _code = string.Empty; /// <summary>
/// 应用程序编码 /// </summary>
[Display( Name = "应用程序编码" )] public string Code { get { return _code == null ? string.Empty : _code.Trim(); } set { _code = value; } } private string _name = string.Empty; /// <summary>
/// 应用程序名称 /// </summary>
[Display( Name = "应用程序名称" )] public string Name { get { return _name == null ? string.Empty : _name.Trim(); } set { _name = value; } } private string _note = string.Empty; /// <summary>
/// 备注 /// </summary>
[Display( Name = "备注" )] public string Note { get { return _note == null ? string.Empty : _note.Trim(); } set { _note = value; } } /// <summary>
/// 启用 /// </summary>
[Display( Name = "启用" )] public bool? Enabled { get; set; } /// <summary>
/// 起始建立时间 /// </summary>
[Display( Name = "起始建立时间" )] public System.DateTime? BeginCreateTime { get; set; } /// <summary>
/// 结束建立时间 /// </summary>
[Display( Name = "结束建立时间" )] public System.DateTime? EndCreateTime { get; set; } /// <summary>
/// 添加描述 /// </summary>
protected override void AddDescriptions() { base.AddDescriptions(); AddDescription( "应用程序编号", ApplicationId ); AddDescription( "应用程序编码", Code ); AddDescription( "应用程序名称", Name ); AddDescription( "备注", Note ); AddDescription( "启用", Enabled.Description() ); AddDescription( "起始建立时间", BeginCreateTime ); AddDescription( "结束建立时间", EndCreateTime ); } } }
最后来总结一下:
1. 领域实体是系统的中心,是业务逻辑的主要放置场所,应该尽可能关闭业务逻辑操做的属性,以免有人能绕过你的方法直接操做数据。
2. DTO是数据传输对象,原义是用来在分布式系统中一次传输更多数据,以减小调用次数,提高性能。
3. 个人DTO用法离原义相去甚远,只是借用了DTO的名词,属于变种。DTO为我解决了以下几个问题:
4. DTO是包含大量属性,没有方法的贫血实体,全部属性都开放getter和setter,以方便模型绑定和序列化。
5. DTO通常状况下是聚合去除方法后的模样,主要好处是方便抽象CRUD及代码生成。
6. DTO位于应用层,由应用层服务操做它。
7. DTO的映射能够采用映射组件,也能够代码生成方便随时修改,以你以为方便为主。
8. 仅在WPF环境下才须要为每一个视图建立一个对应的ViewModel,MVC通常使用DTO便可,仅为复杂界面建立ViewModel。
9. 查询实体是为了配合查询组件引入的构造,目的是帮助查询组件完成空值判断,而且简化表现层的调用。
本文分享了我在几个构造类型上的认识和经验,但愿你们积极讨论,更但愿高手能指正个人不足,帮助我与你们一块儿进步。
.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。
.Net Easyui开发交流QQ群(本群仅限Easyui开发者,非Easyui开发者勿进):157809322
谢谢你们的持续关注,个人博客地址:http://www.cnblogs.com/xiadao521/