一尘不染

如何在考虑可伸缩性和可测试性的同时将域实体正确转换为DTO

spring-boot

我已经阅读了几篇文章和关于将域对象转换为DTO的Stackoverflow帖子,并在我的代码中进行了尝试。在测试和可伸缩性方面,我总是面临一些问题。我知道将域对象转换为DTO的以下三种可能的解决方案。大多数时候我都在使用Spring。

解决方案1:服务层中用于转换的私有方法

第一种可能的解决方案是在服务层代码中创建一个小的“帮助程序”方法,该方法将检索到的数据库对象转换为我的DTO对象。

@Service
public MyEntityService {

  public SomeDto getEntityById(Long id){
    SomeEntity dbResult = someDao.findById(id);
    SomeDto dtoResult = convert(dbResult);
    // ... more logic happens
    return dtoResult;
  }

  public SomeDto convert(SomeEntity entity){
   //... Object creation and using getter/setter for converting
  }
}

优点:

  • 易于实施
  • 不需要额外的转换类->项目不会因实体破裂

缺点:

  • 测试时出现的问题(如new SomeEntity()private方法中使用的那样)以及对象是否深度嵌套,when(someDao.findById(id)).thenReturn(alsoDeeplyNestedObject)如果转换也解决了嵌套结构,我必须提供足够的结果以避免NullPointers

解决方案2:DTO中的其他构造函数,用于将域实体转换为DTO

我的第二个解决方案是向我的DTO实体添加一个附加的构造函数,以在构造函数中转换对象。

public class SomeDto {

 // ... some attributes

 public SomeDto(SomeEntity entity) {
  this.attribute = entity.getAttribute();
  // ... nesting convertion & convertion of lists and arrays
 }

}

优点:

  • 无需额外的转换类
  • 隐藏在DTO实体中的转换->服务代码较小

缺点:

  • new SomeDto()服务代码中的用法,因此我必须提供正确的嵌套对象结构作为someDao模拟结果。

解决方案3:使用Spring的转换器或任何其他外部化的Bean进行此转换

如果最近看到Spring出于转换的原因Converter<S, T>而提供了一个类:但是此解决方案代表进行转换的每个外部化类。使用此解决方案,我将转换器插入到我的服务代码中,当我要将域实体转换成我的DTO时调用它。

优点:

  • 易于测试,因为我可以在测试案例中模拟结果
  • 任务分离->一个专门的班正在做这项工作

缺点:

  • 不会随着我的域模型的增长而“扩展”。对于许多实体,我必须为每个新实体创建两个转换器(->将DTO实体和实体转换为DTO)

您对我的问题有更多解决方案吗?如何处理?您是否为每个新的域对象创建一个新的Converter,并且可以“存活”项目中的类数量?

提前致谢!


阅读 319

收藏
2020-05-30

共1个答案

一尘不染

解决方案1:服务层中用于转换的私有方法

我猜 解决方案1
不能很好地工作,因为您的DTO是面向域的而不是面向服务的。因此,很可能将它们用于不同的服务。因此,一种映射方法不属于一种服务,因此不应在一种服务中实现。您将如何在其他服务中重复使用映射方法?

如果每种服务方法都使用专用的DTO,则1.解决方案将很好地工作。但是最后更多有关此的内容。

解决方案2:DTO中的其他构造函数,用于将域实体转换为DTO

通常,这是一个不错的选择,因为您可以将DTO视为实体的适配器。换句话说:DTO是实体的另一种表示形式。这样的设计通常会包装源对象,并提供使您对被包装的对象有另一种看法的方法。

但是DTO是数据 传输
对象,因此它可能迟早要序列化并通过网络发送,例如使用spring的远程处理功能。在这种情况下,接收此DTO的客户端必须反序列化它,因此即使它仅使用DTO的接口,也需要其类路径中的实体类。

解决方案3:使用Spring的转换器或任何其他外部化的Bean进行此转换

解决方案3是我同样希望的解决方案。但是我将创建一个Mapper<S,T>接口,负责从源到目标的映射,反之亦然。例如

public interface Mapper<S,T> {
     public T map(S source);
     public S map(T target);
}

可以使用诸如modelmapper的映射框架来完成实现。


您还说过,每个实体都有一个转换器

不会随着我的域模型的增长而“扩展”。对于许多实体,我必须为每个新实体创建两个转换器(->将DTO实体和实体转换为DTO)

我确实要为一个DTO创建2个转换器或一个映射器,因为您的DTO是面向域的。

一旦开始在其他服务中使用它,您将认识到该其他服务通常应该或不能返回第一个服务所执行的所有值。您将开始为彼此的服务实现另一个映射器或转换器。

编辑

关于第三个解决方案:您希望在哪里调用映射器?

在用例上方的层中。DTO是数据传输对象,因为它们将数据打包在最适合传输协议的数据结构中。因此,我将该层称为传输层。该层负责将用例的请求和结果对象与传输表示形式进行映射,例如json数据结构。

2020-05-30