초보자를 위한 DTO 기본 틀

서여·2025년 4월 29일
post-thumbnail

백엔드 개발을 하다보면 dto에 대해 자주 보게 된다. 하지만 나같은 개발 초보에겐 dto는 정확히 무엇인지, 어떻게, 어디에 사용해야할지 막막해서 결국 받아들이기를 포기하는 것 같다. 그래서 개발 초보 입장에서 바라보고 나름 공부해본 dto에 대해서 정리해보겠다.


DTO란?

DTO란 Data Tranfer Object로 프로세스 간에 데이터를 전달하는 객체를 의미한다.

프로그래밍을 하면서 한 메서드에서 다른 메서드로 결과 혹은 값을 전달해야하는 경우가 빈번하게 발생한다. 이때, 전달하는 값들이 어떤 집합이고 어떤 의미를 가지고 있는지 DTO를 이용해 묶어주면 코드의 가독성이 높아지게 된다.

DTO를 잘 사용하기 위해선 DTO의 목적에 대해 잘 생각해야 한다고 생각한다.

DTO의 목적은 프로세스 간에 데이터를 전달하는 것이다. 그렇다면 A프로세스에서 B프로세스로 넘어갈 때, 넘겨주어야 할 데이터를 DTO로 묶어서 보내면 된다.

변수를 직접 파라미터로 넘겨줘도 되지만, 각 데이터의 집합이 무엇을 의미하는지 모호해질 수도 있고, 메서드에 다른 변수가 추가되었을 때 수정해야 할 범위가 늘어난다. 따라서 변수가 한 개일지라도 DTO로 감싸 확장에 대비하는 것이 좋다.


DTO는 언제/어떻게 사용해야돼?

DTO는 읽기 및 쓰기 프로세스로 나누어 사용 방법을 정할 수 있다.

1. 읽기 프로세스

읽기 프로세스의 핵심은 사용자에게 값을 반환해야한다는 것이다.
읽기 프로세스는 요청 → 처리 → 응답 순서이므로 각 과정마다 DTO로 담아서 보내면 유지보수성 및 가독성이 좋아진다.

  • Request DTO: 클라이언트가 서버로 보낼 때 사용. 사용자가 보낸 정보를 파싱해서 Request DTO에 담으면 된다.
  • Param DTO: 중간 로직(Service → Repository)에서 필요한 값을 전달할 때 사용.
  • Response DTO: 서버가 클라이언트로 응답할 때 사용.

Request → Param → Entity → Response 순서로 데이터는 이동하고 각각이 DTO가 된다.

2. 쓰기 프로세스

쓰기 프로세스의 핵심은 사용자의 값을 DB에 저장 혹은 삭제 해야한다는 것이다.
읽기 프로세스는 요청 → 처리 → 저장/삭제 순서이므로 각 과정마다 DTO로 담아서 보내면 좋다.

  • Request DTO: 외부 요청을 받을 때 사용.
  • Command DTO: 비지니스 로직을 거칠 때 사용.
  • Entity: DB 저장용 도메인 모델.

DTO를 사용하기 모호할땐, 위처럼 읽기/쓰기 프로세스의 구조를 기반으로 DTO를 사용하면 훨씬 더 명확하게 사용이 가능하다.


일관성 있는 API를 위한 DTO 사용법

백엔드는 프론트에게 JSON 형식으로 데이터를 보내준다. 이때 통일된 응답으로 프론트에게 보내주면 가독성 및 협업에 더 좋다.

형식은 다음과 같다.

CommonResponse<T> DTO는 아래와 같은 정보를 가진다.

{
	"status": 200,
  	"message": "성공",
  	"data": {...}
}

controller의 반환값은 CommonResponse<T>로 통일하게 되면, 일관성 있는 API를 만들어 사용할 수 있다.

다음은 Controller 예시이다.

package com.yeoli.yeolpost.controller;

@RestController
@RequiredArgsConstructor
public class PostController {

  private final PostService postService;

  @PostMapping(value = "api/posts")
  // CommonResponse<T> 사용하여 API 형식 통일
  public CommonResponse<Void> createPost(@RequestBody @Valid PostCreateRequest request) {
  // 쓰기 프로세스 이므로, Request - Command - Response 구조 사용
    postService.savePost(request.toCommand());
    return new CommonResponse<>(200, "게시글 작성 성공", null);
  }

  @GetMapping(value = "/api/posts")
  // CommonResponse<T> 사용하여 API 형식 통일
  public CommonResponse<PostSummaryListResponse> getAllPosts() {
    return new CommonResponse<>(200, "게시글 조회 성공", postService.getAllPosts());
  }

  @GetMapping(value = "/api/posts", params = "title")
  // CommonResponse<T> 사용하여 API 형식 통일
  public CommonResponse<PostListResponse> getPosts(
      @RequestParam("title") @Valid PostSearchRequest request) {
     // 읽기 프로세스이므로, Request - Param - Response 구조 사용, 하지만 간단한 프로세스이므로 Param DTO 생략
    return new CommonResponse<>(200, "게시글 검색 성공", postService.getPostsByTitle(request));
  }
}

좋은 DTO 네이밍 규칙

DTO의 네이밍 방법에 대해서 알아보겠다. DTO를 사용하기로 마음먹었지만 개념도 추상적이라 이름을 짓기가 더 어려울 수 있다. 따라서 DTO의 네이밍 규칙에 대해서 정리해보았다.

우선 DTO의 이름을 보고 알 수 있어야하는 정보는 역할과 목적이다. 따라서 아래 두 가지 원칙을 준수하여 네이밍하면 더 좋은 DTO 명이 될 것이다.

1. '역할'에 따라 접미사를 붙여라

아까 프로세스에 따라 나누었듯이, 요청(Request), 응답(Response), 내부 전달(Command, Param), 응답(Response)를 접미사로 붙이면 어느 과정에서 필요한 DTO인지 바로 파악이 가능하다.

2. '무엇'을 위한 DTO인지를 붙여라

Request DTO를 예로 들면, 게시물을 생성하기 위한 Request일수도, 또는 게시물을 단건으로 조회하기 위한 Request일수도, 게시물 목록 전체를 조회하기 위한 Request일 수도 있다. 따라서 무엇을 위한 DTO인지도 구별해주기 위해 아래와 같은 네이밍을 이용하면 좋다.

CreatePostRequest   // 생성용
UpdatePostRequest   // 수정용
PostDetailResponse  // 단건 조회용
PostListResponse    // 목록 응답용

추가로 List<단일 조회 Response dto>로 결과를 반환한다면, 이는 PostListResponse라는 DTO로 한 번 더 감싼 뒤 반환하면 가독성과 확장가능성이 높은 코드를 만들 수 있다.


글쓰기 너무 어렵다... 이번 글도 두서없이 적었지만 그래도 어쨌든 완성해서 뿌듯하다. 글쓰기 실력이 늘 수 있을까🥺

profile
안녕하세요:) 아키텍트가 되고 싶은 백엔드 개발자 지망생입니다.

1개의 댓글

comment-user-thumbnail
2025년 4월 29일

글 잘 읽었습니다~! 마지막 좋은 DTO 네이밍 규칙은 출처가 따로 있을까요~? 아님 개인적인 의견인지 궁금합니다 :)

답글 달기