将DTO映射到域对象的最佳实践?

时间:2009-03-24 16:36:43

标签: .net asp.net design-patterns dto

我已经看到很多与将DTO映射到域对象相关的问题,但我觉得他们没有回答我的问题。我之前使用过很多方法并且有自己的看法,但我正在寻找一些更具体的方法。

情况:

我们有很多域对象。我们使用CSLA模型,因此我们的域对象可能非常复杂,并且包含自己的数据访问。你不想在线上传递这些。我们将编写一些新服务,以多种格式(.Net,JSON等)返回数据。出于这个(以及其他原因),我们还创建了一个精简的数据传输对象,以便在线路上传递。

我的问题是如何连接DTO和域对象?

我的第一反应是使用Fowler, DTO pattern-type solution。我已经多次看过这件事了,对我来说感觉很对。域对象不包含对DTO的引用。调用外部实体(“映射器”或“汇编器”)以从域对象创建DTO。通常在域对象端有一个ORM。这样做的缺点是“映射器”在任何真实情况下都会变得非常复杂,并且可能非常脆弱。

提出的另一个想法是让域对象“包含”DTO,因为它只是一个精益数据对象。域对象属性将在内部引用DTO属性,如果需要,可以返回DTO。我可以看到没有问题,但感觉不对。我看过一些使用NHibernate的人似乎使用这种方法的文章。

还有其他方法吗?以上是值得使用的方式之一吗?如果是,如果没有,为什么?

感谢您提前了解。

11 个答案:

答案 0 :(得分:35)

当您仅支持单个映射时,使用位于域和DTO之间的映射器的好处并不明显,但随着映射数量的增加,将该代码与域隔离有助于保持域更简单和更精简。你不会因为额外的重量而混乱你的域名。

就个人而言,我尝试将映射保留在我的域实体之外,并将责任放在我称之为“管理器/服务层”的内容中。这是位于应用程序和存储库之间的层,并提供业务逻辑,例如工作流协调(如果修改A,则可能还必须修改B,因此服务A将与服务B一起使用)。

如果我有很多可能的结尾格式,我可能会考虑创建一个可以使用访问者模式的可插入格式化程序,例如转换我的实体,但我还没有找到任何复杂的东西。< / p>

答案 1 :(得分:22)

您可以使用the one written by Jimmy Bogard之类的自动播放器,它在对象之间没有连接,并依赖于遵守的命名约定。

答案 2 :(得分:6)

我们使用T4模板来创建映射类。

Pro's - 在编译时可用的人类可读代码,比运行时映射器更快。 100%控制代码(可以使用部分方法/模板模式在ad-hoc基础上扩展功能)

Con's - 排除某些属性,域对象集合等,学习T4语法。

答案 3 :(得分:2)

您如何看待在DTO类中实现构造函数,该构造函数将域对象作为参数?

说......像这样的东西

class DTO {

     // attributes 

     public DTO (DomainObject domainObject) {
          this.prop = domainObject.getProp();
     }

     // methods
}

答案 4 :(得分:1)

另一种可能的解决方案:http://glue.codeplex.com

特点:

  • 双向映射
  • 自动映射
  • 不同类型之间的映射
  • 嵌套映射和展平
  • 列表和数组
  • 关系验证
  • 测试映射
  • 属性,字段和方法

答案 5 :(得分:0)

您还可以尝试使用对象映射器Otis。概念类似于NHibernate映射(属性或XML)。

http://code.google.com/p/otis-lib/wiki/GettingStarted

答案 6 :(得分:0)

我可以推荐一个我创建的工具,并在CodePlex上托管开源:EntitiesToDTOs

从DTO到实体的映射,反之亦然,是通过扩展方法实现的,这些方法组成了每一端的汇编器端。

您的代码如下:

Foo entity = new Foo();
FooDTO dto = entity.ToDTO();
entity = dto.ToEntity();

List<Foo> entityList = new List<Foo>();
List<FooDTO> dtoList = entityList.ToDTOs();
entityList = dtoList.ToEntities();

答案 7 :(得分:0)

为什么我们不能这样做?

class UserDTO {
}

class AdminDTO {
}

class DomainObject {

 // attributes
 public DomainObject(DTO dto) {
      this.dto = dto;
 }     

 // methods
 public function isActive() {
      return (this.dto.getStatus() == 'ACTIVE')
 }

 public function isModeratorAdmin() {
      return (this.dto.getAdminRole() == 'moderator')
 }

}


userdto = new UserDTO();
userdto.setStatus('ACTIVE');

obj = new DomainObject(userdto)
if(obj.isActive()) {
   //print active
}

admindto = new AdminDTO();
admindto.setAdminRole('moderator');

obj = new DomainObject(admindto)
if(obj.isModeratorAdmin()) {
   //print some thing
}

@FrederikPrijck (或)有人:请建议。在上面的示例中,DomainObject依赖于DTO。通过这种方式,我可以避免代码映射dto&lt; - &gt; domainObject的。

或DomainObject类可以扩展DTO类吗?

答案 8 :(得分:0)

另一种选择是使用ModelProjector。它支持所有可能的情况,并且非常容易使用且占用空间最小。

答案 9 :(得分:0)

我们可以为此使用Factory,Memento和Builder模式。工厂隐藏有关如何从DTO创建域模型实例的详细信息。 Memento将注意域模型与DTO之间的序列化/反序列化,甚至可以访问私有成员。 Builder将允许使用流畅的界面从DTO映射到域。

答案 10 :(得分:0)

将映射逻辑保留在实体内部意味着您的域对象现在知道了不需要了解的“实现细节”。通常,DTO是您通往外界的网关(通过传入请求或通过读取外部服务/数据库)。由于实体是您的业务逻辑的一部分,因此最好将这些详细信息保留在实体之外。

将映射保留在其他位置是唯一的选择-但是应该去哪里?我曾尝试引入映射对象/服务,但毕竟已经说完了,这似乎是过度工程(可能是)。对于较小的项目,我已经使用Automapper取得了一些成功,但是诸如Automapper之类的工具都有其自身的陷阱。我很难找到与映射有关的问题,因为Automapper的映射是隐式的,并且与其余代码完全脱钩了(不像“关注点分离”,而是更像是“被抛弃的映射在哪里生活”),所以它们有时很难追踪。并不是说Automapper没有它的用​​途,因为它有用途。我只是认为映射应该尽可能明显和透明,以避免出现问题。

除了创建映射服务层之外,我在将映射保留在DTO中也取得了很多成功。由于DTO始终位于应用程序的边界,因此可以使他们了解业务对象,并弄清楚如何在它们之间进行映射。即使映射的数量缩放到合理的数量,它也可以正常工作。所有映射都在一个地方,您不必在数据层,反腐败层或表示层内部管理一堆映射服务。相反,映射只是委派给与请求/响应有关的DTO的实现细节。由于序列化程序通常仅在通过网络发送属性和字段时才对属性和字段进行序列化,因此您不会遇到任何问题。就我个人而言,我发现这是最干净的选择,并且根据我的经验,我可以说它在大型代码库上可很好地扩展。