DTO란? (responseDTO, requestDTO)

윤현일·2025년 2월 15일

오늘은 아주아주 기본적인 DTO에 관해 말해보겠다.
플젝 처음할 때는 무슨 소린가 했는데, 간단한 예시로 이해를 했다.

1. DTO란?

DTO(Data Transfer Object)는 계층 간 데이터를 전달하기 위한 객체이다. 주로 Controller - Service - Repository 사이에서 데이터를 정리해서 깔끔하게 주고받을 때 사용된다. 이로써 불필요한 데이터 노출을 막을 수 있다.

한마디로 API 요청마다 필요한 정보만 주고 받는다는 말이다.

여기서 사용자는 화면을 보고있고, 화면(view)는 Controller -> Service -> Repository -> DataBase
이런 계층 구조로 작동 하고있다.

근데 정보를 다 주면 좋은거지, 왜 필요한 data만 주고 받아야 하는건가?

2. DTO의 역할

  • 데이터를 최소한으로 전달: 필요한 데이터만 주고받아서 속도가 빨라진다.
  • 보안 강화: 데이터베이스(Entity)와 직접 연결되지 않아, 중요한 정보가 노출되지 않는다.
  • 유지보수 용이: 데이터 구조가 바뀌어도 코드 수정이 쉽다.

간단한 예시로 살펴보자

3. DTO 예시 - 인스타그램

예를 들어, 인스타그램에서 특정 프로필을 조회하고자 한다.
이때, 프로필(User)엔티티에는 많은 정보가 있다.

  • 이름
  • 아이디
  • 비밀번호
  • 성별
  • 팔로워 수
  • 팔로잉 수
  • 프로필 사진
  • 소개
    등등등

이 외에도 많은 필드가 있지만, 특정 프로필을 조회하는데에는

아이디, 프로필 사진, 이름, 게시물 수, 팔로워, 팔로잉 만 필요하다.
(1번 이유)

또한, 비밀번호, 프로필 세부 설정 등 남에게 보여서는 안되는 민감한 정보도 존재한다.
(2번 이유)

그러기 때문에, 응답을 할 때 User 객체를 다 주는게 아닌, DTO에 특정 정보만 감싸서 보내야 한다.

추가로, 특정 프로필 조회와 달리 인스타그램에서 스토리 형식으로 보여질 때도 프로필 정보가 조회 된다.
이때에, 아이디와 프로필 사진, 스토리 업로드 및 조회 유무만 필요하다.

이처럼 다른 형식의 데이터를 전달하기에 유용하기에 DTO가 사용 된다.

4. requestDTO, responseDTO

ReqeustDTO

requestDTO는 클라이언트에서 서버로 데이터를 보낼 때 사용하는 객체다. 사용자가 입력한 데이터를 안전하게 서버로 전달할 수 있도록 도와주고, 아까 말한대로 필요한 정보만 쏙쏙 전달 하는 기능을 한다.

ResponseDTO

responseDTO는 서버에서 클라이언트로 데이터를 보낼 때 사용하는 객체다. API의 응답 데이터를 깔끔하게 정리하고, 불필요한 정보를 제외하여 클라이언트가 원하는 데이터만 받을 수 있도록 한다.

아까와 같은 예시로, 특정 프로필을 조회하는 API가 있다고 하자.

이때에 클라이언트(사용자)는 userId를 서버에게 넘겨줘야한다.

 public ProfileResponseDTO getUserProfile(Long userId) {
        User user = profileRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("User not found"));
        return new ProfileResponseDTO(user.getId(), user.getProfileImage(), user.getFollowerCount(), user.getFollowingCount());
    }

그러면 서버는 Controller -> Service -> Repository -> DataBase 를 거쳐서 userId를 찾고,

Service -> Controller 를 거쳐서
아이디, 프로필 사진, 팔로워/팔로잉 등을 클라이언트에게 전달 한다.

public class ProfileResponseDTO {
    private Long userId;
    private String profileImage;
    private int followerCount;
    private int followingCount;

    public ProfileResponseDTO(Long userId, String profileImage, int followerCount, int followingCount) {
        this.userId = userId;
        this.profileImage = profileImage;
        this.followerCount = followerCount;
        this.followingCount = followingCount;
    }

    public Long getUserId() {
        return userId;
    }
    
    public String getProfileImage() {
        return profileImage;
    }
    
    public int getFollowerCount() {
        return followerCount;
    }
    
    public int getFollowingCount() {
        return followingCount;
    }
}

5. Converter

컨버터(Converter)는 DTO와 Entity 간 변환을 쉽게 하기 위한 도구이다. 일반적으로 서비스 계층에서 DTO를 Entity로 변환하거나, Entity를 DTO로 변환하는데, 이때 컨버터를 활용하면 코드의 중복을 줄이고 유지보수를 편리하게 할 수 있다. 즉 역할 분리를 위해 클래스를 하나 더 선언해서 쉽게 변환 할 수 있다는 말이다.
코드 예시 :

public class ProfileConverter {
    public ProfileResponseDTO convertToDTO(User user) {
        return new ProfileResponseDTO(
            user.getId(),
            user.getProfileImage(),
            user.getFollowerCount(),
            user.getFollowingCount()
        );
    }
    
    public User convertToEntity(ProfileResponseDTO dto) {
        return new User(
            dto.getUserId(),
            dto.getProfileImage(),
            dto.getFollowerCount(),
            dto.getFollowingCount()
        );
    }

service 코드 :

@Service
public class ProfileService {
    private final ProfileRepository profileRepository;
    private final ProfileConverter profileConverter;
    
   
    public ProfileResponseDTO getUserProfile(Long userId) {
        User user = profileRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("User not found"));
        return profileConverter.convertToDTO(user);
    }
}

사실 컨버터의 경우 사용되는 위치는 자유롭게 정해도 된다고 생각한다. 나는 비즈니스 로직을 담당하는 Service 계층에서 빌더 패턴을 주로 사용하는 편이다.
혹은 변환할 필드가 적을 때는 사용하지 않기도 한다.

profile
라이틔얼

1개의 댓글

comment-user-thumbnail
2025년 2월 15일

hoxy record로 안 쓰는 이유가 있으신가용 (레고드만 써서 혹시 레코드 별로인 부분이 있나 하는 순수한 궁금증)

답글 달기