gpt4 book ai didi

java - 如何在考虑可扩展性和可测试性的同时正确地将域实体转换为 DTO

转载 作者:IT老高 更新时间:2023-10-28 13:51:56 25 4
gpt4 key购买 nike

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

方案一:服务层的私有(private)方法进行转换

第一个可能的解决方案是在服务层代码中创建一个小的“帮助程序”方法,它将检索到的数据库对象转换为我的 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 mock 。

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

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

优点:

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

缺点:

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

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

提前致谢!

最佳答案

Solution 1: Private method in the service layer for converting

我猜解决方案 1 不会很好用,因为您的 DTO 是面向域的而不是面向服务的。因此,它们很可能用于不同的服务。所以映射方法不属于一个服务,因此不应该在一个服务中实现。您将如何在另一个服务中重用映射方法?

如果您为每种服务方法使用专用的 DTO,则 1. 解决方案会很有效。但最后会详细介绍。

Solution 2: Additional constructor in the DTO for converting domain entity to DTO

通常是一个不错的选择,因为您可以将 DTO 视为实体的适配器。换句话说:DTO 是实体的另一种表示。此类设计通常会包装源对象并提供方法,让您可以从另一个角度查看被包装的对象。

但是 DTO 是一个数据 transfer 对象,因此它迟早会被序列化并通过网络发送,例如使用 spring's remoting capabilities .在这种情况下,接收此 DTO 的客户端必须对其进行反序列化,因此需要其类路径中的实体类,即使它仅使用 DTO 的接口(interface)。

Solution 3: Using Spring's Converter or any other externalized Bean for this converting

解决方案 3 也是我更喜欢的解决方案。但我会创建一个 Mapper<S,T>负责从源映射到目标的接口(interface),反之亦然。例如

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

可以使用像 modelmapper 这样的映射框架来完成实现。 .


你还说每个实体都有一个转换器

doesn't "scale" that much as my domain model grows. With a lot of entities I have to create two converters for every new entity (-> converting DTO entitiy and entitiy to DTO)

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

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

如果我从专用或共享 DTO 的优缺点开始,这个答案会很长,所以我只能请你阅读我的博客 pros and cons of service layer designs .

编辑

About the third solution: where do you prefer to put the call for the mapper?

在用例之上的层。 DTO 是数据传输对象,因为它们将数据打包成最适合传输协议(protocol)的数据结构。因此,我称该层为传输层。该层负责将用例的请求和结果对象从传输表示映射到传输表示,例如json 数据结构。

编辑

I see you're ok with passing an entity as a DTO constructor parameter. Would you also be ok with the opposite? I mean, passing a DTO as an Entity constructor parameter?

一个好问题。相反的对我来说是不行的,因为我会在实体中引入对传输层的依赖。这意味着传输层的变化会影响实体,我不希望更详细的层的变化影响更多的抽象层。

如果你需要将数据从传输层传递到实体层,你应该应用依赖倒置原则。

引入一个接口(interface),该接口(interface)将通过一组getter返回数据,让DTO实现它并在实体构造函数中使用该接口(interface)。请记住,此接口(interface)属于实体层,因此不应依赖于传输层。

                                interface
+-----+ implements || +------------+ uses +--------+
| DTO | ---------------||-> | EntityData | <---- | Entity |
+-----+ || +------------+ +--------+

关于java - 如何在考虑可扩展性和可测试性的同时正确地将域实体转换为 DTO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47843039/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com