Spring Boot中的DTO对流模式

Mak*_*iuk 7 java entity design-patterns dto spring-boot

主要问题是如何在不违反SOLID原则的情况下将DTO转换为实体以及将实体转换为Dto。 例如,我们有这样的json:

{ id: 1,
  name: "user", 
  role: "manager" 
} 
Run Code Online (Sandbox Code Playgroud)

DTO是:

public class UserDto {
 private Long id;
 private String name;
 private String roleName;
}
Run Code Online (Sandbox Code Playgroud)

实体是:

public class UserEntity {
  private Long id;
  private String name;
  private Role role
} 
public class RoleEntity {
  private Long id;
  private String roleName;
}
Run Code Online (Sandbox Code Playgroud)

并且有有用的Java 8 DTO转换模式

但是在他们的示例中,没有一对多关系。为了创建UserEntity,我需要使用dao层(服务层)按RoleName获取Role。我可以将UserRepository(或UserService)注入对流器中吗?因为转换器组件似乎会破坏SRP,所以它只能转换,不能知道服务或存储库。

转换器示例:

@Component
public class UserConverter implements Converter<UserEntity, UserDto> {
   @Autowired
   private RoleRepository roleRepository;    

   @Override
   public UserEntity createFrom(final UserDto dto) {
       UserEntity userEntity = new UserEntity();
       Role role = roleRepository.findByRoleName(dto.getRoleName());
       userEntity.setName(dto.getName());
       userEntity.setRole(role);
       return userEntity;
   }

   ....
Run Code Online (Sandbox Code Playgroud)

在对流器类中使用存储库好吗?还是应该创建另一个服务/组件来负责从DTO(例如UserFactory)创建实体?

roo*_*eee 6

尽量将转换与其他层解耦:

public class UserConverter implements Converter<UserEntity, UserDto> {
   private final Function<String, RoleEntity> roleResolver;

   @Override
   public UserEntity createFrom(final UserDto dto) {
       UserEntity userEntity = new UserEntity();
       Role role = roleResolver.apply(dto.getRoleName());
       userEntity.setName(dto.getName());
       userEntity.setRole(role);
       return userEntity;
  }
}

@Configuration
class MyConverterConfiguration {
  @Bean
  public Converter<UserEntity, UserDto> userEntityConverter(
               @Autowired RoleRepository roleRepository
  ) {
    return new UserConverter(roleRepository::findByRoleName)
  }
}
Run Code Online (Sandbox Code Playgroud)

您甚至可以定义自定义,Converter<RoleEntity, String>但这可能会将整个抽象扩展得有点过头。

正如其他一些人指出的那样,这种抽象隐藏了应用程序的一部分,该部分在用于集合时可能表现不佳(因为 DB 查询通常可以批处理。我建议您定义一个Converter<List<UserEntity>, List<UserDto>>在转换单个对象,但您现在可以批量处理您的数据库请求,而不是一个一个查询——用户不能错误地使用所述转换器(假设没有恶意)。

如果您希望在定义转换器时更加舒适,请查看MapStructModelMapper。最后但并非最不重要的一点是给datus一个机会(免责声明:我是作者),它可以让您以流畅的方式定义您的映射,而无需任何隐式功能:

@Configuration
class MyConverterConfiguration {

  @Bean
  public Mapper<UserDto, UserEntity> userDtoCnoverter(@Autowired RoleRepository roleRepository) {
      Mapper<UserDto, UserEntity> mapper = Datus.forTypes(UserDto.class, UserEntity.class)
        .mutable(UserEntity::new)
        .from(UserDto::getName).into(UserEntity::setName)
        .from(UserDto::getRole).map(roleRepository::findByRoleName).into(UserEntity::setRole)
        .build();
      return mapper;
  }
}
Run Code Online (Sandbox Code Playgroud)

(这个例子在转换一个 Collection<UserDto>

我认为这将是最可靠的方法,但给定的上下文/场景受到无法提取的依赖关系和性能影响,这让我认为强制 SOLID 在这里可能是一个坏主意。这是一个权衡


dav*_*xxx 3

如果您有服务层,那么使用它来进行转换或将任务委托给转换器会更有意义。
理想情况下,转换器应该只是转换器:映射器对象,而不是服务。
现在,如果逻辑不太复杂并且转换器不可重用,您可以将服务处理与映射处理混合在一起,在这种情况下,您可以将前缀替换ConverterService.

而且,如果只有服务与存储库进行通信,那就更好了。
Otherwise layers become blur and the design messy : we don't know really any longer who invokes who.

我会这样做:

controller -> service -> converter 
                      -> repository
Run Code Online (Sandbox Code Playgroud)

或自行执行转换的服务(转换不太复杂且不可重用):

controller -> service ->  repository            
Run Code Online (Sandbox Code Playgroud)

现在说实话,我讨厌 DTO,因为这些只是数据重复。
我仅介绍它们,因为客户在信息方面的要求与实体表示不同,并且拥有自定义类(在本例中不是重复的)会变得更加清晰。

  • DTO 是一种不可避免的罪恶,因为它们提供了与客户端的隔离层。我正在将 web 应用程序与没有服务或 api 层的服务解耦。这是非常痛苦的 (3认同)