복붙노트

[SPRING] 확장 성 및 테스트 가능성을 고려하면서 도메인 엔터티를 DTO로 올바르게 변환하는 방법

SPRING

확장 성 및 테스트 가능성을 고려하면서 도메인 엔터티를 DTO로 올바르게 변환하는 방법

도메인 객체를 DTO로 변환하기위한 여러 기사와 Stackoverflow 게시물을 읽고 코드에서 사용해 보았습니다. 테스트와 확장성에 관해서는 항상 몇 가지 문제에 직면하고 있습니다. 도메인 객체를 DTO로 변환하기위한 다음 세 가지 가능한 솔루션을 알고 있습니다. 대부분 나는 봄을 사용하고 있습니다.

해결책 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
  }
}

장점 :

단점 :

솔루션 2 : 도메인 엔티티를 DTO로 변환하기위한 DTO의 추가 생성자

두 번째 솔루션은 생성자의 객체를 변환하기 위해 DTO 엔티티에 추가 생성자를 추가하는 것입니다.

public class SomeDto {

 // ... some attributes

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

}

장점 :

단점 :

해결 방법 3 : 변환을 위해 Spring의 변환기 또는 다른 외부화 된 Bean 사용

최근 Spring이 이유를 변환하기위한 클래스를 제공한다는 것을 보았습니다 : Converter 하지만이 솔루션은 변환을 수행하는 모든 외부 클래스를 나타냅니다. 이 솔루션을 사용하면 서비스 코드에 변환기를 주입하고 도메인 엔터티를 DTO로 변환하려고 할 때 호출합니다.

장점 :

단점 :

내 문제에 대한 해결책이 더 많습니까? 어떻게 처리합니까? 모든 새 도메인 개체에 대해 새 변환기를 만들고 프로젝트의 클래스 수와 함께 "살 수 있습니까?

미리 감사드립니다.

해결법

  1. ==============================

    1.솔루션 1은 잘 작동하지 않을 것입니다. 왜냐하면 DTO가 도메인 지향적이며 서비스 지향적이지 않기 때문입니다. 따라서 서로 다른 서비스에 사용되기 쉽습니다. 따라서 매핑 방법은 하나의 서비스에 속하지 않으므로 하나의 서비스에 구현하면 안됩니다. 다른 서비스에서 어떻게 매핑 방법을 재사용 하시겠습니까?

    솔루션 1은 잘 작동하지 않을 것입니다. 왜냐하면 DTO가 도메인 지향적이며 서비스 지향적이지 않기 때문입니다. 따라서 서로 다른 서비스에 사용되기 쉽습니다. 따라서 매핑 방법은 하나의 서비스에 속하지 않으므로 하나의 서비스에 구현하면 안됩니다. 다른 서비스에서 어떻게 매핑 방법을 재사용 하시겠습니까?

    서비스 방법마다 전용 DTO를 사용하면 1. 솔루션이 잘 작동합니다. 그러나 이것에 관해서는 결국.

    DTO를 엔티티에 대한 어댑터로 볼 수 있기 때문에 일반적으로 좋은 옵션입니다. 즉, DTO는 엔티티의 또 다른 표현입니다. 이러한 디자인은 원본 개체를 래핑하고 래핑 된 개체에 대한 다른보기를 제공하는 메서드를 제공하기도합니다.

    그러나 DTO는 데이터 전송 객체이므로 조만간 직렬화하여 네트워크를 통해 전송할 수 있습니다. 스프링의 리모팅 기능을 사용합니다. 이 경우이 DTO를 수신하는 클라이언트는 DTO의 인터페이스 만 사용하는 경우에도이를 역 직렬화해야하므로 클래스 경로에 엔티티 클래스가 필요합니다.

    솔루션 3은 내가 선호하는 솔루션입니다. 하지만 소스에서 대상으로 그리고 그 반대로 매핑하는 책임이있는 Mapper 인터페이스를 만들 것입니다. 예 :

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

    구현은 modelmapper와 같은 매핑 프레임 워크를 사용하여 수행 할 수 있습니다.

    당신은 또한 각 엔티티에 대한 변환기

    DTO가 도메인 지향적이기 때문에 하나의 DTO에 대해서만 2 변환기 또는 하나의 매퍼를 만들어야한다는 것을 의아하게 생각합니다.

    다른 서비스에서이 서비스를 사용하자 마자 다른 서비스는 일반적으로 첫 번째 서비스가 수행하는 모든 값을 반환하거나 반환 할 수 없다는 것을 알게됩니다. 다른 서비스마다 다른 매퍼 또는 변환기를 구현하기 시작합니다.

    필자가 헌신적이고 공유 된 DTO의 장단점으로 시작한다면이 답변은 오래 갈 것입니다. 따라서 블로그 서적과 서비스 계층 디자인의 단점을 읽으라고 요청할 수 있습니다.

  2. ==============================

    2.나는 받아 들인 대답에서 세 번째 해법을 좋아한다.

    나는 받아 들인 대답에서 세 번째 해법을 좋아한다.

    그리고 이런 식으로 DtoConverter를 만듭니다. BaseEntity 클래스 마커 :

    public abstract class BaseEntity implements Serializable {
    }
    

    AbstractDto 클래스 마커 :

    public class AbstractDto {
    }
    

    GenericConverter 인터페이스 :

    public interface GenericConverter<D extends AbstractDto, E extends BaseEntity> {
    
        E createFrom(D dto);
    
        D createFrom(E entity);
    
        E updateEntity(E entity, D dto);
    
        default List<D> createFromEntities(final Collection<E> entities) {
            return entities.stream()
                    .map(this::createFrom)
                    .collect(Collectors.toList());
        }
    
        default List<E> createFromDtos(final Collection<D> dtos) {
            return dtos.stream()
                    .map(this::createFrom)
                    .collect(Collectors.toList());
        }
    
    }
    

    CommentConverter 인터페이스 :

    public interface CommentConverter extends GenericConverter<CommentDto, CommentEntity> {
    }
    

    CommentConveter 클래스 구현 :

    @Component
    public class CommentConverterImpl implements CommentConverter {
    
        @Override
        public CommentEntity createFrom(CommentDto dto) {
            CommentEntity entity = new CommentEntity();
            updateEntity(entity, dto);
            return entity;
        }
    
        @Override
        public CommentDto createFrom(CommentEntity entity) {
            CommentDto dto = new CommentDto();
            if (entity != null) {
                dto.setAuthor(entity.getAuthor());
                dto.setCommentId(entity.getCommentId());
                dto.setCommentData(entity.getCommentData());
                dto.setCommentDate(entity.getCommentDate());
                dto.setNew(entity.getNew());
            }
            return dto;
        }
    
        @Override
        public CommentEntity updateEntity(CommentEntity entity, CommentDto dto) {
            if (entity != null && dto != null) {
                entity.setCommentData(dto.getCommentData());
                entity.setAuthor(dto.getAuthor());
            }
            return entity;
        }
    
    }
    
  3. ==============================

    3.제 생각에는 세 번째 해결책이 최선의 방법입니다. 네, 각 엔티티에 대해 두 개의 새로운 변환 클래스를 만들어야하지만 테스트를 위해 시간을 내면 많은 두통을 겪지는 않을 것입니다. 처음에는 코드를 적게 작성한 다음 코드를 테스트하고 유지 보수 할 때 훨씬 더 많은 것을 작성할 수있는 솔루션을 선택해서는 안됩니다.

    제 생각에는 세 번째 해결책이 최선의 방법입니다. 네, 각 엔티티에 대해 두 개의 새로운 변환 클래스를 만들어야하지만 테스트를 위해 시간을 내면 많은 두통을 겪지는 않을 것입니다. 처음에는 코드를 적게 작성한 다음 코드를 테스트하고 유지 보수 할 때 훨씬 더 많은 것을 작성할 수있는 솔루션을 선택해서는 안됩니다.

  4. ==============================

    4.나는 마술 매핑 라이브러리 나 외부 변환기 클래스를 사용하지 않고 각각의 엔티티에서 필요로하는 각각의 DTO로 메소드를 변환 한 자신 만의 작은 빈을 추가했다. 그 이유는 매핑이 다음과 같았 기 때문입니다.

    나는 마술 매핑 라이브러리 나 외부 변환기 클래스를 사용하지 않고 각각의 엔티티에서 필요로하는 각각의 DTO로 메소드를 변환 한 자신 만의 작은 빈을 추가했다. 그 이유는 매핑이 다음과 같았 기 때문입니다.

    어리석게 간단하고 나는 단지 하나의 필드에서 다른 유틸리티로 약간의 값을 복사 할 것입니다.

    또는 매우 복잡하고 커스텀 매개 변수에서 일반적인 매핑 라이브러리에 쓰는 것이 더 복잡 할 것입니다. 예를 들어 클라이언트가 JSON을 보낼 수 있지만 후드에서는 이것이 엔티티로 변환되고 클라이언트가이 엔티티의 부모 객체를 다시 검색하면 다시 JSON으로 변환됩니다.

    즉, 필자는 DTD를 다시 가져 오기 위해 엔티티 컬렉션에서 .map (converter :: convert)를 호출 할 수 있음을 의미합니다.

    하나의 클래스에서 모든 것을 확장 할 수 있습니까? 일반 매핑을 사용하는 경우에도이 매핑에 대한 사용자 지정 구성을 어딘가에 저장해야합니다. 이 코드는 대개 매우 간단합니다. 소수의 경우를 제외하고는이 클래스가 복잡성에 대해 너무 걱정하지 않습니다. 나는 또한 더 많은 엔티티가있을 것으로 기대하지 않지만, 만약 내가했다면이 변환기를 서브 도메인 당 클래스로 그룹화 할 수 있습니다.

    엔티티 및 DTO에 기본 클래스를 추가하여 일반 변환기 인터페이스를 작성하고 클래스별로 구현할 수 있습니다. 아직 필요하지 않습니다.

  5. from https://stackoverflow.com/questions/47843039/how-to-properly-convert-domain-entities-to-dtos-while-considering-scalability by cc-by-sa and MIT license