본문 바로가기
Java/트러블 슈팅

DTO <-> Entity 어디서 변환해야 할까?

by oneny 2023. 11. 3.

DTO <-> Entity 어디서 변환해야 할까?

프로젝트를 진행하면서 DTO와 Enity를 어디서 변환하면 좋을지 생각했고, 이 주제는 개발에 있어 다들 많이 하는 고민 중 하나인 것 같다. 데이터베이스 Entity와 클라이언트 또는 API에서 사용하는 DTO(Data Transfer Object) 변환 과정은 데이터 구조 및 비즈니스 로직 차이를 해결하는 데 도움이 된다. 따라서 변환 과정은 개발에 중요한 요소 중 하나라고 생각한다. 그러면 먼저 DTO는 무엇이고, Entity가 무엇인지부터 간단히 살펴보자.

 

Entity

Entity는 데이터 모델링에서 레코드 또는 데이터베이스 테이블에서 식별 가능한 하나의 항목을 나타내는 개념이다. Entity는 특정 도메인에서 중요한 정보 단위를 나타내며 데이터베이스에서 해당 도메인의 정보를 저장하고 관리하기 위해 사용된다.

 

DTO(Data Transfer Object)

DTO는 계층간 데이터 교환을 위해 사용하는 객체를 말한다. 일반적으로 웹 애플리케이션에서 서버와 클라이언트 간의 데이터를 전송할 때 비즈니스 로직을 담지 않고, 단순히 데이터를 저장하는 역할로 DTO를 사용한다.

 

DTO를 사용해야 하는 이유

  1. 보안상의 이유
    • 엔티티를 그대로 사용하면, 엔티티의 모든 데이터가 노출된다. 따라서 민강함 정보가 노출될 가능성이 높다.
    • DTO를 사용하면 민감한 정보를 필터링하거나 숨길 수 있다.
  2. API의 안정성
    • 엔티티를 그대로 사용하면, 엔티티의 데이터가 변경되면 API 응답 데이터도 변경된다. 따라서 프론트엔드에서는 API 응답 데이터를 사용하는 모든 곳에 변경된 데이터를 추적하고 수정해야 하는 번거로움을 초래할 수 있다.
    • DTO를 사용하면 엔티티와 API 응답 데이터 간에 독립성을 유지하고, 엔티티의 변경이 API에 영향을 미치지 않게 된다.
  3. 데이터 전송 최적화
    • 엔티티를 사용하게 되면 불필요한 데이터가 포함되어, API 응답 데이터의 크기가 커진다.
    • 따라서 DTO를 사용하면 필요한 데이터만 포함하여 데이터베이스 쿼리의 결과를 클라이언트로 전송할 수 있어 엔티티보다 더 경량화된 형태로 데이터를 전송할 수 있다.

즉, DTO는 엔티티와 클라이언트 간의 중간 계층으로 사용되어 보안, 안정성, 유지보수, 성능 등 다양한 측면에서 혜택을 제공할 수 있다.

 

Layered Architecture

그러면 클라이언트로부터 전송된 데이터가 DTO가 되고, 어느 계층에서 Entity로 변환하는 것이 좋을까? 이에 대해 말하기 위해서는 Layered Architecture에 대해서 알아야 한다. Layered Architecture는 소프트웨어 개발에서 일반적으로 가장 많이 사용되는 아키텍처이다. Layer의 수에 따라 N Layered Architecture라고 불려지는데 각 Layer은 애플리케이션 내에서의 특정 역할과 관심사 별로 구분되는 것이다.

 

Presentation Layer

사용자로부터 데이터를 수집하고 화면에 정보를 표시하는 것을 주 관심사로 둔다. Presentation Layer는 비즈니스 로직이 어떻게 수행되는지 알 필요가 없다. 대표적인 구성요소는 View와 Controller가 있다.

 

Business Layer

비즈니스 로직을 수행하는 것을 주 관심사로 둔다. 마찬가지로 화면에 데이터를 출력하는 방법이나 혹은 데이터를 어디서, 어떻게 가져오는지에 대한 내용은 알고있지 않다. 그저 Data Access Layer에서 데이터를 가져와 비즈니스 로직을 수행하고, 그 결과를 Presentation Layer로 전달하면 된다. 대표적인 구성요소는 Service가 있다.

 

Data Access Layer

데이터베이스에 액세스하여 데이터를 가져오고 다루는 것을 주 관심사로 둔다. 대표적인 구성요소로는 Repository가 있다.

 

DTO와 Entity 변환 과정

 

Controller Layer에서 Entity <-> DTO 변환 작업

@PostMapping("/signup")
public ResponseEntity<ResponseMessage> signup(@RequestBody SignUpMemberDto signUpMemberDto) {
    memberService.signUp(signUpMemberDto.toEntity());

    return ResponseEntity.ok(ResponseMessage.builder()
        .status(SUCCESS)
        .build());
}

 

@MemberLoginCheck
@GetMapping("/myInfo")
public ResponseEntity<ResponseMessage> findMyInfo(HttpSession session) {
    Long memberId = SessionUtil.getLoginMemberId(session);

    Member member = memberService.getMemberInfo(memberId);
    MyInfoMemberDto myInfoMemberDto = MyInfoMemberDto.from(member);

    return ResponseEntity.ok(ResponseMessage.builder()
        .status(SUCCESS)
        .result()
        .build());
}

 

위 코드를 살펴보면 Service Layer는 인자로 Entity만 받거나 Controller에게 Entity로 반환하고, Controller는 Domain을 DTO로 변환해 View에게 응답을 보낸다.

이러한 방식은 Service Layer에서 Entity를 바로 받게 함으로써, Service Layer은 Entity에만 의존하기 때문에 코드 재사용성이 높아진다는 장점이 있다. 그런데 꼭 DTO와 Entity 간의 변환 위치가 Controller(표현 계층)이어야 할까? 코드의 재사용이 장점이라고 하지만 정말 재사용할 만큼 여러 종류의 컨트롤러가 해당 서비스를 이용할지 의문이다.

또한, View에 반환할 필요가 없는 데이터까지 Entity 객체에 포함되어 Controller에 넘어오는 등의 문제점이 있다. 그래서 Service Layer에서 DTO로 변환하는 작업을 해보기로 한다.

 

 

Service Layer에서 Entity <-> DTO 변환 작업

public Long signUp(SignUpMemberDto signUpMemberDto) {

    if (isDuplicatedMember(signUpMemberDto)) {
        throw new EmailDuplicateException("이미 가입한 이메일입니다. 입력된 이메일: %s".formatted(signUpMemberDto.getEmail()));
    }

    Member member = signUpMemberDto.toEntity();
    member.setBcryptPassword(passwordEncoder.encode(member.getPassword()));
    return memberRepository.insertMember(member);
}

 

public MyInfoMemberDto getMemberInfo(Long memberId) {
    Member member = memberRepository.findById(memberId)
        .orElseThrow(() -> new MemberNotFoundException("존재하지 않는 회원입니다."));

    return MyInfoMemberDto.from(member);
}

위 코드를 살펴보면 Service Layer는 인자로 DTO를 받아 Entity로 변환하거나 Controller에게 DTO를 반환한다.

이러한 방식은 위의 단점을 상쇄할 수 있고, Entity로 변환하기 전에 유효성 검증이나 위에서 볼 수 있듯이 이미 가입한 이메일인지 DTO에 대한 상태를 검증하여 사용자의 입력 정보를 확인할 수 있다. 또한 위 예시는 간단하게 DTO를 Entity를 구성하기는 쉽지 않다. Repository를 통해 여러 부수적인 정보들을 조회하여 Entity 객체를 구성할 수 있는 경우도 존재한다. Controller에서 이러한 작업을 하게 되면 여러 Service에 의존할 수 있기 때문에 Service Layer가 Entity로 변환시키는 것이 더 나은 방안이라고 생각한다.

 

결론

DTO와 Entity를 변환하는 데에 있어 정해진 답은 없는 것 같다. 원칙적으로는 여러 종류의 컨트롤러에서 해당 서비스를 이용하기 위해서 Service Layer에 DTO가 들어오면 안될 수 있다. 하지만 실제로는 한 종류의 컨트롤러가 서비스를 사용하기 때문에 엄격히 제한할 필요는 없다고 생각하고, Service Layer에서 DTO가 진입한 후 메서드 상위에서 DTO 체크 및 Entity로 변환하여 사용하도록 구현하였다.

 

 

 

 

 

 

 

출처

DTO의 사용 범위에 대하여

DTO의 사용범위는 어디까지? 또, DTO 변환은 어디서?

DTO(Data Transaction Object) with NestJS

Layered Architecture(feat. MVC 패턴)

계층화 아키텍처(Layerd Architecture)