我应该将DTO映射到客户端和服务器端的域实体吗?

Jay*_*Jay 16 .net mapping orm serialization dto

我有一个丰富的域模型,其中大多数类都有一些行为和一些属性,可以计算或公开成员对象的属性(也就是说这些属性的值永远不会被持久化).

我的客户端只通过WCF与服务器通话.

因此,对于每个域实体,我有一个相应的DTO - 一个只包含数据的简单表示 - 以及一个mapper类,它实现DtoMapper<DTO,Entity>并可以通过静态网关将实体转换为DTO等效或反之亦然:

var employee = Map<Employee>.from_dto<EmployeeDto>();
Run Code Online (Sandbox Code Playgroud)

此应用程序的服务器端主要是持久性,我的DTO从WCF服务进入,反序列化,然后任意ORM将它们持久存储到数据库,或者来自WCF的查询请求和ORM执行该查询数据库并返回要由WCF序列化和发回的对象.

鉴于这种情况,将持久性存储映射到域实体是否有意义,或者我应该直接映射到DTO?

如果我使用域实体,那么流程就是

  1. 客户请求对象
  2. WCF将请求发送到服务器
  3. ORM查询数据库并返回域实体
  4. 域映射实体转换为DTO的域实体
  5. WCF序列化DTO并返回客户端
  6. 客户端反序列化DTO
  7. DTO通过映射器转换为域实体
  8. viewmodels创建

回程类似

如果我直接映射到DTO,我可以根据请求消除每个对象的一个​​映射.这样做我会失去什么?

唯一想到的是在插入/更新之前验证的另一个机会,因为我不能保证DTO在被发送之前经过验证或者甚至作为域实体存在,我想有机会验证select(如果另一个进程可能在数据库中放置了无效值).还有其他原因吗?这些原因是否足以保证额外的映射步骤?

编辑:

我之前确实说过"任意ORM",我确实希望事情尽可能与ORM和持久性无关,但是如果你有任何特殊的东西可以添加到NHibernate,那么一定要做.

Aar*_*ght 25

我个人建议你在服务器端保留你的映射.你可能已经做了很多工作来建立你现在的设计点; 不要扔掉它.

考虑一下Web服务是什么.它不仅仅是对你的ORM的抽象; 这是一份合同.它是您的客户的公共API,包括内部和外部.

公共API应该几乎没有任何改变的理由.除了添加新的类型和方法之外,几乎对API的任何更改都是一个重大变化.但是你的域名模型不会那么严格.当您添加新功能或发现原始设计中的缺陷时,您将需要不时更改它.您希望能够确保对内部模型的更改不会导致通过服务合同进行级联更改.

这实际上是一种常见的做法(我不会用"最佳实践"这一短语来侮辱读者)为类似的原因创建每条消息的特定RequestResponse类; 扩展现有服务和方法的能力变得更加简单,而不会破坏它们.

客户端可能不希望您在服务中内部使用完全相同的模型.如果你是你唯一的客户,那么这似乎是透明的,但是如果你有外部客户并且已经看到他们对你的系统的解释经常会有多远,那么你就会明白不让你的完美模型泄漏的价值超出服务API的范围.


有时,甚至不可能通过API发回模型.出现这种情况的原因有很多:

  • 对象图中的循环.OOP完美无缺; 序列化带来灾难性的.你最终必须做出痛苦的永久性选择,关于图形必须序列化的"方向".另一方面,如果你使用DTO,你可以按照你想要的任何方向序列化,无论什么适合手头的任务.

  • 尝试在SOAP/REST上使用某些类型的继承机制充其量只是一个障碍.旧式XML序列化程序至少支持xs:choice; DataContract没有,我不会对基本原理进行狡辩,但足以说明你的富域模型中可能有一些多态性,并且几乎不可能通过Web服务引导它.

  • 延迟/延迟加载,如果使用ORM,可能会使用它.确保它正确序列化是很尴尬的 - 例如,使用Linq到SQL实体,WCF甚至不会触发延迟加载器,它只会放入null该字段,除非您手动加载它 - 但问题变得更糟重新进入的数据.像List<T>在构造函数中初始化的自动属性一样简单- 在域模型中很常见 - 在WCF中不起作用,因为它不会调用构造函数.相反,你必须添加一个[OnDeserializing]初始化方法,你真的不想用这个垃圾混乱你的域模型.

  • 我也注意到你使用NHibernate的括号.考虑到像IList<T>Web服务一样无法序列化的接口!如果你像我们大多数人一样使用NHibernate的POCO类,那么这根本就不起作用了.


当您的内部域模型与客户端的需求不匹配时,可能会出现很多情况,更改域模型以满足这些需求是没有意义的.作为一个例子,让我们采取像发票一样简单的事情.它需要显示:

  • 有关帐户的信息(帐号,姓名等)
  • 特定于发票的数据(发票号,日期,截止日期等)
  • A/R级信息(以前的余额,滞纳金,新余额)
  • 发票上所有内容的产品或服务信息;
  • 等等.

这可能适合域模型.但是,如果客户想要运行显示1200张这些发票的报告呢?某种和解报告?

这很容易序列化.现在,您要发送1200张发票,其中相同的数据被反复序列化 - 相同的帐户,相同的产品,相同的应收帐款.在内部,您的应用程序正在跟踪所有链接; 它知道发票#35和发票#45是针对同一客户的,因此共享一个Customer参考; 所有这些信息在序列化时都会丢失,您最终会发送大量冗余数据.

你真正想要的是发送一个自定义报告,其中包括:

  • 报告中包含的所有帐户及其A/R;
  • 报告中包含的所有产品;
  • 所有发票,仅包含产品和帐户ID.

如果要避免大量冗余,则需要在将传出数据发送到客户端之前对其执行额外的"规范化".这非常有利于DTO方法; 在您的域模型中使用此结构是没有意义的,因为您的域模型已经以自己的方式处理冗余.

我希望这些足够的例子和足够的理由说服你保持域< - >服务合同的映射完好无损.到目前为止,你已经完成了正确的事情,你有一个伟大的设计,否定所有努力支持可能导致后来出现重大问题的事情将是一种耻辱.