programing

확장성과 테스트성을 고려하면서 도메인 엔티티를 DTO로 적절하게 변환하는 방법

magicmemo 2023. 3. 22. 20:58
반응형

확장성과 테스트성을 고려하면서 도메인 엔티티를 DTO로 적절하게 변환하는 방법

도메인 오브젝트를 DTO로 변환하기 위한 여러 기사와 Stackoverflow 투고를 읽고 코드로 테스트했습니다.테스트와 scalability에 관해서는 항상 몇 가지 문제에 직면해 있습니다.도메인 객체를 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
  }
}

장점:

  • 구현하기 쉽다
  • 변환을 위한 추가 클래스가 필요 없음 -> 프로젝트가 엔티티와 함께 중단되지 않음

단점:

  • 시 (「」등)new SomeEntity()되어 있는 .when(someDao.findById(id)).thenReturn(alsoDeeplyNestedObject)되는 경우 하려면 Null Pointers를 사용합니다.

솔루션 2: 도메인 엔티티를 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: 이 변환에는 스프링 컨버터 또는 기타 외장 Bean을 사용합니다.

이유로 된 :Converter<S, T>이 솔루션은 변환을 수행하는 모든 외부화된 클래스를 나타냅니다.이 솔루션에서는 변환기를 서비스 코드에 삽입하고 도메인 엔티티를 DTO로 변환하고 싶을 때 호출합니다.

장점:

  • 테스트 케이스에서 결과를 조롱할 수 있기 때문에 테스트하기 쉽다.
  • 태스크 분리 -> 전용 클래스가 작업을 수행하고 있습니다.

단점:

  • 도메인 모델이 성장함에 따라 "확장"되지 않습니다.엔티티가 많은 경우 새로운 엔티티마다 2개의 컨버터를 작성해야 합니다(-> DTO 자격 및 DTO 자격 변환).

제 문제에 대한 더 많은 해결책이 있나요? 그리고 어떻게 대처하나요?새로운 도메인 오브젝트마다 새로운 Converter를 생성하여 프로젝트의 클래스 수에 대응할 수 있습니까?

잘 부탁드립니다!

솔루션 1: 서비스 계층에서 변환을 위한 프라이빗 방식

솔루션 1은 DTO가 서비스 지향적이지 않고 도메인 지향적이기 때문에 제대로 작동하지 않을 것입니다.따라서 다른 서비스에서 사용될 수 있습니다.따라서 매핑 방식은 1개의 서비스에 속하지 않으므로 1개의 서비스에 구현하지 마십시오.다른 서비스에서 매핑 방법을 재사용하려면 어떻게 해야 합니까?

1. 서비스 방식별로 전용 DTO를 사용하면 솔루션이 제대로 작동합니다.하지만 마지막에 이것에 대해 더 많이.

솔루션 2: 도메인 엔티티를 DTO로 변환하기 위한 추가 컨스트럭터

일반적으로 DTO를 엔티티에 대한 어댑터로 볼 수 있기 때문에 좋은 옵션입니다.즉, DTO는 엔티티의 또 다른 표현입니다.이러한 설계에서는 소스 객체를 래핑하고 래핑된 객체에 대해 다른 뷰를 제공하는 메서드를 제공하는 경우가 많습니다.

그러나 DTO는 데이터 전송 객체이기 때문에 조만간 직렬화되어 네트워크를 통해 전송될 수 있습니다(예: 스프링 원격 기능 사용).이 경우 이 DTO를 수신한 클라이언트는 DTO의 인터페이스만 사용하는 경우에도 DTO를 역직렬화해야 하므로 클래스 경로에 엔티티 클래스가 필요합니다.

해결책 3: 이 변환에는 스프링 컨버터 또는 기타 외장 Bean을 사용합니다.

3번지 나는 '아예'를 것이다.Mapper<S,T>gg로 、 는는 e e e 、 e e e e e e e e e 예:

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

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


또한 각 엔티티의 컨버터가

도메인 모델이 성장함에 따라 "확장"되지 않습니다.엔티티가 많은 경우 새로운 엔티티마다 2개의 컨버터를 작성해야 합니다(-> DTO 자격 및 DTO 자격 변환).

DTO는 도메인 지향이기 때문에 2개의 컨버터 또는 1개의 DTO용 매퍼만 작성하면 됩니다.

다른 서비스에서 이 서비스를 사용하기 시작하면 보통 다른 서비스에서 첫 번째 서비스가 반환하는 모든 값을 반환해야 하는지 반환할 수 없는지를 알 수 있습니다.다른 서비스에 대해 다른 매퍼 또는 컨버터 구현을 시작합니다.

이 답변은 전용 또는 공유 DTO의 장단점부터 시작하면 길어지기 때문에 서비스 레이어 설계에 대한블로그의 장단점을 읽어보셔야 합니다.

편집

세 번째 해결책에 대해: 매퍼에 대한 문의는 어디에 하고 싶으십니까?

사용 사례 위의 계층에서.DTO는 전송 프로토콜에 가장 적합한 데이터 구조로 데이터를 패킹하기 때문에 데이터 전송 개체입니다.이 레이어를 트랜스포트 레이어라고 부릅니다.이 계층은 전송 표현(예: json 데이터 구조)과의 사이에서 사용 사례의 요청 및 결과 개체를 매핑하는 역할을 합니다.

편집

엔티티를 DTO 생성자 매개 변수로 전달해도 됩니다.그 반대도 괜찮으시겠어요?DTO를 엔티티 생성자 매개 변수로 전달하는 것?

좋은 질문입니다.그 반대는 적절하지 않습니다.그 후, 엔티티의 의존성을 트랜스포트 레이어에 도입할 것이기 때문입니다.이는 전송 계층의 변경이 엔티티에 영향을 미칠 수 있음을 의미하며, 더 자세한 계층의 변경이 더 추상적인 계층에 영향을 미치는 것은 원치 않습니다.

전송 계층에서 엔티티 계층으로 데이터를 전달해야 하는 경우 종속성 반전 원칙을 적용해야 합니다.

일련의 getter를 통해 데이터를 반환하는 인터페이스를 도입하여 DTO가 구현하고 엔티티 컨스트럭터에서 이 인터페이스를 사용하도록 합니다.이 인터페이스는 엔티티의 계층에 속하므로 전송 계층에 종속되어서는 안 됩니다.

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

저는 인정된 답변 중 세 번째 해결책이 좋습니다.

해결책 3: 이 변환에는 스프링 컨버터 또는 기타 외장 Bean을 사용합니다.

제가 창작합니다.DtoConverter다음과 같이 합니다.

BaseEntity 클 base :

public abstract class BaseEntity implements Serializable {
}

AbstractDto 클래스 마커:

public class AbstractDto {
}

Generic Converter 인터페이스:

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

}

Comment Converter 인터페이스:

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

Comment Converter 클래스의 실장:

@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;
    }

}

나는 결국 마법 지도 라이브러리나 외부 변환기 클래스를 사용하지 않고, 단지 내 자신의 작은 콩을 추가했다.convert각 엔티티에서 필요한 각 DTO에 대한 메서드를 제공합니다.이 다음과같았기 입니다.

어리석게도 단순하고, 한 필드에서 다른 필드로 몇 가지 값을 복사하거나, 아마도 작은 유틸리티 방법으로.

커스텀 파라미터로 범용 매핑라이브러리에 기입하는 것은 코드를 기입하는 것보다 복잡합니다.예를 들어 클라이언트가 JSON을 전송할 수 있지만 후드 아래에서 이것은 엔티티로 변환되며 클라이언트가 이러한 엔티티의 상위 개체를 다시 가져오면 다시 JSON으로 변환됩니다.

, 그냥 전화하면 죠..map(converter::convert)에서 DTO 수 .

모든 것을 하나의 클래스에 포함시킬 수 있습니까?이 매핑의 커스텀 설정은 범용 매퍼를 사용하는 경우에도 어딘가에 저장해야 합니다.몇 가지 경우를 제외하고 코드는 일반적으로 매우 간단하기 때문에 이 클래스가 복잡해질까 봐 크게 걱정하지 않습니다.또한 엔티티가 수십 개 늘어날 것으로 예상되지는 않지만, 엔티티가 수십 개 늘어날 경우 이러한 변환기를 서브도메인별로 클래스로 그룹화할 수 있습니다.

기본 클래스를 엔티티 및 DTO에 추가하여 범용 컨버터 인터페이스를 작성하고 클래스별로 구현할 수 있도록 하는 것은 저에게도 필요하지 않습니다(아직은).

제 생각에는 세 번째 해결책이 최선인 것 같습니다.네, 각 엔티티에 대해 2개의 변환 클래스를 새로 만들어야 하지만 테스트 시간이 되면 큰 문제는 없을 것입니다.처음부터 코드를 적게 쓰고 그 코드를 테스트 및 유지보수할 때는 더 많이 쓰는 솔루션을 선택하지 마십시오.

하나의 두의존관계를 있는 를 알 수 EntityManagerDto에 접속하여 의존관계가 로드되었는지 여부를 확인합니다.는 Dto에 대해 모르기 에 이 에 들지 .EntityManager개인적으로는 솔루션으로서Converters그러나 동시에 나는 같은 엔티티에 대해 여러 개의 Dto 클래스를 갖는 것을 선호합니다.를 들어,가 100%라고하면, 「」는 100%라고 확신해 주세요.User Entity대응하지 않고 로드됩니다.Company그럼, 그 다음,UserDto그것은 가지고 있지 않다CompanyDto밭으로.동시에 내가 그걸 안다면UserEntity로딩됩니다.Company그럼 aggregate pattern을 사용합니다.UserCompanyDto포함하는 클래스UserDto그리고.CompanyDto파라미터로서

언급URL : https://stackoverflow.com/questions/47843039/how-to-properly-convert-domain-entities-to-dtos-while-considering-scalability

반응형