web-dev-qa-db-ja.com

Spring BootのDTO conveterパターン

主な質問は、DTOをエンティティに変換する方法とエンティティをDtosに変換する方法です壊すことなく [〜#〜] solid [〜#〜] 原則。
たとえば、次のようなjsonがあります。

{ id: 1,
  name: "user", 
  role: "manager" 
} 

DTOは次のとおりです。

public class UserDto {
 private Long id;
 private String name;
 private String roleName;
}

そしてエンティティは:

public class UserEntity {
  private Long id;
  private String name;
  private Role role
} 
public class RoleEntity {
  private Long id;
  private String roleName;
}

そして便利な Java 8 DTO conveterパターン があります。

しかし、それらの例ではOneToMany関係はありません。 UserEntityを作成するには、daoレイヤー(サービスレイヤー)を使用してroleNameでRoleを取得する必要があります。 UserRepository(またはUserService)をconveterに注入できますか?コンバーターコンポーネントが壊れるようです [〜#〜] 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;
   }

   ....

Conveterクラスでリポジトリを使用するのは良いですか?または、DTOからエンティティを作成する(UserFactoryなど)別のサービス/コンポーネントを作成する必要がありますか?

10

変換を他のレイヤーから可能な限り切り離すようにしてください:

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)
  }
}

カスタムConverter<RoleEntity, String>を定義することもできますが、これにより抽象化全体が少し拡張される可能性があります。

他の人が指摘したように、この種の抽象化は、コレクションに使用すると非常にパフォーマンスが低下する可能性があるアプリケーションの一部を隠します(DBクエリは通常バッチ処理される可能性があるためです。Converter<List<UserEntity>, List<UserDto>>を定義することをお勧めします単一のオブジェクトを変換するのは面倒ですが、1つずつクエリを実行する代わりにデータベース要求をバッチ処理できるようになりました-ユーザーは間違ったコンバーターを使用できません(悪意がないと想定)。

コンバーターを定義するときにさらに快適にしたい場合は、 MapStruct または ModelMapper を参照してください。そして最後に 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;
  }
}

(この例は、Collection<UserDto>を変換するときに、依然としてdbボトルネックの影響を受けます。

私はこれが最もSOLIDアプローチであると主張しますが、与えられたコンテキスト/シナリオは、パフォーマンスに影響を与える抽出できない依存関係に苦しんでいるため、強制的にSOLIDここでは悪い考えかもしれません。それはトレードオフです

3
roookeee

サービス層がある場合は、それを使用して変換を行うか、タスクをコンバーターに委任する方が理にかなっています。
理想的には、コンバーターは単なるコンバーターである必要があります。サービスではなくマッパーオブジェクトです。
これで、ロジックが複雑すぎず、コンバーターが再利用できない場合、サービス処理とマッピング処理を混在させることができます。この場合、ConverterプレフィックスをServiceに置き換えることができます。

また、サービスのみがリポジトリと通信する場合も、より良いように見えます。
そうしないと、レイヤーがぼやけてデザインが乱雑になります。だれが誰を呼び出すかは、もはやわかりません。

私はこのように物事を行います:

controller -> service -> converter 
                      -> repository

またはそれ自体が変換を実行するサービス(変換は複雑すぎず、再利用できません):

controller -> service ->  repository            

正直に言うと、これらは単なるデータの複製なので、DTOは嫌いです。
私はそれらを紹介します。情報の点でクライアントの要件がエンティティの表現と異なり、カスタムクラス(この場合は重複ではない)を持っていることが本当に明確になるからです。

3
davidxxx

個人的には、コンバーターはコントローラーとサービスの間にある必要があります。DTOが心配する必要があるのは、サービスレイヤーのデータと、コントローラーに公開する情報の方法だけです。

controllers <-> converters <-> services ... 

あなたのケースでは、永続化層でユーザーのロールを設定するためにJPAを利用できます。

3
stacker

それは私がそうするであろう方法です。私がそれを概念化する方法は、ユーザーコンバーターがユーザー/ユーザーのdto変換を担当するということです。したがって、当然のことながら、それは役割/役割のdto変換を担当すべきではありません。あなたの場合、ロールリポジトリは、ユーザーコンバーターが委任しているロールコンバーターとして暗黙的に機能しています。たぶん、SOLIDについてより深い知識を持っている人なら、私が間違っていれば私を正してくれるかもしれませんが、個人的にはそれをチェックしたように感じます。

しかし、私が気になるのは、DBオペレーションへの変換の概念を必ずしも直感的ではないという事実に縛られているためです。今後数か月または数年先の一部の開発者には注意が必要です。パフォーマンスの考慮事項を理解せずに誤ってコンポーネントを取得して使用することはありません(とにかく、より大きなプロジェクトで開発していると仮定します)。キャッシュロジックを組み込んだロールリポジトリの周りにいくつかのデコレータクラスを作成することを検討します。

1
TyrusB

Mapstruct を使用して、直面しているこの種のエンティティからdtoへの変換の問題を解決することをお勧めします。アノテーションプロセッサを介して、dtoからentityへのマッピング、およびその逆のマッピングが自動的に生成され、通常のリポジトリ(@Autowired)。

this の例をチェックして、ニーズに合っているかどうかを確認することもできます。

1
gkanellis

個別のコンバータークラスを作成する代わりに、Entityクラス自体にその責任を与えることができます。

public class UserEntity {
    // properties

    public static UserEntity valueOf(UserDTO userDTO) {
        UserEntity userEntity = new UserEntity();
        // set values;
        return userEntity;
    }

    public UserDTO toDto() {
        UserDTO userDTO = new UserDTO();
        // set values
        return userDTO;
    }
}

使用法;

UserEntity userEntity = UserEntity.valueOf(userDTO);
UserDTO userDTO = userEntity.toDto();

このようにして、ドメインを1か所にまとめます。 Spring BeanUtilsを使用してプロパティを設定できます。 ORMツールを使用してUserEntityをロードするときに、RoleEntityについても同じことを行い、遅延読み込みを行うかどうかを決定できます。

0
jonarya