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


81

我已经看到了许多与将DTO映射到域对象有关的问题,但是我不觉得它们回答了我的问题。我以前使用过很多方法,也有自己的见解,但我正在寻找更具体的东西。

情况:

我们有很多领域对象。我们使用CSLA模型,因此我们的域对象可能非常复杂,并且它们包含自己的数据访问权限。您不想通过导线传递这些信息。我们将要编写一些新服务,这些服务将以多种格式(.Net,JSON等)返回数据。由于这个原因(和其他原因),我们还创建了一个精益的数据传输对象,以在线上传递。

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

我的第一个反应是使用Fowler DTO模式类型的解决方案。我已经看过很多次了,这对我来说感觉很对。域对象不包含对DTO的引用。调用外部实体(“映射器”或“汇编器”)以从域对象创建DTO。通常,在域对象方面有一个ORM。不利的一面是,“映射器”在任何实际情况下都变得极其复杂,并且可能非常脆弱。

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

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


4
自动映射器看起来很有趣。在替换之前,我已经看过很多代码。我的主要问题是,如果出于某种原因我将被大量的映射代码所困扰,我宁愿自己对其进行控制。
Brian Ellis

2
当我们从DTO转到域对象时,该映射是100%手动的。由于我们试图使域对象保持基于操作的状态,而不仅仅是数据容器,因此这是一个很难解决的问题。去一个DTO,这是容易解决的问题。
Jimmy Bogard,2009年

另一个选择是ServiceToolkit.NET的Beta版,它是我们在上一个项目中启动的。也许它可以帮助您:http

我同意这是错误的,因为域对象应该不了解dto对象。尽管在这种情况下它们可能是相关的,但它们的目的是完全分开的(dto通常是为达到目的而设计的),并且您将创建不必要的依赖关系。
Sinaesthetic

Answers:


40

当您仅支持单个映射时,将映射器放在您的域和DTO之间的好处就不那么明显了,但是随着映射数量的增加,将代码与域隔离可以使域更简单,更精简。您不会因过多的负担而使您的域名混乱。

就我个人而言,我尝试将映射排除在我的域实体之外,并将责任放在我所谓的“经理/服务层”中。这是一个位于应用程序和存储库之间的层,并提供诸如工作流协调之类的业务逻辑(如果您修改A,则可能还必须修改B,以便服务A与服务B一起使用)。

如果我有很多可能的结尾格式,我可能会考虑创建一个可插入的格式化程序,该格式程序可以使用Visitor模式,例如来转换我的实体,但是我还没有发现需要这种复杂的格式。


“(如果您修改A,则可能还必须修改B,以便服务A与服务B一起使用)”-这不属于业务逻辑吗?我认为这部分应该交给控制器,而不是服务?
Ayyappa

24

您可以使用一种自动映射器,例如Jimmy Bogard编写的那种,在对象之间没有连接,并且依赖于遵守的命名约定。


9
Automapper可能会导致意外暴露的属性->安全漏洞。最好明确指出应作为DTO公开的内容。
守护进程2010年

4
@deamon:确实值得关注,但是编写所有粘糊糊的映射代码所产生的错误(以及由于人为疏忽造成的潜在安全漏洞)也是如此。我将走上自动道路,并使用内置的自定义映射功能处理5%的费用。
Merritt 2010年

@deamon-您是否可以为不应该公开的属性做条件映射?认为AutoMapper可以处理这种情况?
理查德B

如果您使用AutoMapper,我认为拥有所有单元测试以测试映射是否正确完成非常重要。
L四四

7

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

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

缺点-排除某些属性,域对象的集合等,学习T4语法。


6

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

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

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


3

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

说...像这样

class DTO {

     // attributes 

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

     // methods
}

9
请不要这样做。您不希望DTO层知道或依赖您的域层。映射的优势在于,可以通过更改映射轻松地切换下层,或者可以通过更改映射来控制下层的修改。假设dtoA今天映射到domainObjectA,但是明天的要求是它映射到domainObjectB。在您的情况下,您必须修改DTO对象,这是很大的禁止。您已经失去了许多映射器的好处。
Frederik Prijck

2
首先,谢谢!:D。因此@FrederikPrijck通过在DTO和之间插入一个层DomainObject,我们基本上致力于解决DTO的问题取决于域对象,因此所有“构建工作”都在称为的中间层(类)中完成,该中间层mapper依赖于两个DTO和DomainObjects。那么这是最好的方法,还是通常推荐的方法?我只要求确保了解这一点。
维克多

4
是的,该层称为“汇编程序”。通过使用第3层来定义映射,您可以通过另一种实现轻松替换汇编器层(例如:删除Automapper并使用手动映射),这总是一个更好的选择。理解它的最佳方法是考虑我将在哪里给您对象A,而其他人给您对象B。您无权访问每个对象(只有dll),因此映射只能通过创建第三个对象来完成。层。但是,即使您可以访问任何对象,也应始终在外部进行映射,因为它们是不相关的。
弗雷德里克·普里克

1
但是,实际上,此答案带有注释和更正“无用”,它为所有读者带来有关该问题的认可和提示。.它确实有助于学习,我不知道为什么要投票..它可以帮助我..但我不想就此开始讨论。无论如何,谢谢你的回答。
维克多

3
实际上,我喜欢这种方法,当前我使用构造函数将实体映射到DTO,并使用mapper类将输入dto映射到实体。
dream83619

1

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

特征:

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


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();

这在架构上是错误的,因为您使DTO和域实体彼此了解。
拉斐尔

5
@Raffaeu我不这么认为,因为ToDTO / ToDTOs / ToEntity / ToEntities方法被定义为表示汇编程序的扩展方法。将实体转换为DTO以及反之亦然的逻辑是在扩展方法(汇编程序)中,而不是在实体/ DTO中。
kzfabi 2012年

2
如果您谈论“汇编程序”,请以正确的方式实施它们。使它们模块化,使其易于交换,使用依赖注入。域模型本身不需要知道已转换为DTO。假设我有1个域对象,但50个使用同一域的不同应用程序,每个都有自己的DTO。您不会创建50个扩展。相反,您将为每个应用程序创建一个应用程序服务,并将必要的汇编程序作为依赖项注入到服务中。
弗雷德里克·普里克

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映射到<->域对象。

还是DomainObject类可以扩展DTO类?



0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.