트러블슈팅

Response DTO에서 Builder와 생성자를 구분해서 사용한 이유

sudaruuu 2026. 3. 30. 15:36

문제 상황

응답 DTO를 작성하던 중,
builder와 생성자를 혼용해서 사용하고 있다는 점을 인지하게 되었다.

@Getter
@Builder
public class MenuResponse {

    private Long menuId;
    private String name;
    private BigDecimal price;

    public static MenuResponse from(Menu menu) {
        return MenuResponse.builder()
                .menuId(menu.getId())
                .name(menu.getName())
                .price(menu.getPrice())
                .build();
    }
}
@Getter
public class PopularMenuResponse {

    private Long menuId;
    private String name;
    private Long orderCount;

    public PopularMenuResponse(Long menuId, String name, Long orderCount) {
        this.menuId = menuId;
        this.name = name;
        this.orderCount = orderCount;
    }
}

 

두 DTO 모두 단순히 데이터를 담는 객체임에도 불구하고

생성 방식이 서로 달라,

어떤 기준으로 builder와 생성자를 선택해야 하는지 고민이 생겼다.

 

특히,

  • builder로 통일하는 것이 맞는지
  • 아니면 생성자를 사용하는 것이 더 적절한지

명확한 기준이 없다는 점이 문제였다.


고민 과정

처음에는 단순히 코드 스타일의 문제라고 생각했다.

 

"어차피 DTO인데 하나로 통일하면 더 깔끔하지 않을까?"

라는 생각으로 builder로 통일하는 것도 고려했다.

 

하지만 조회 로직을 구현하면서

집계 결과를 DTO로 바로 매핑하는 과정에서 의문이 생겼다.

 

"이 경우에도 builder를 쓰는 게 맞을까?"

 

이 질문을 기준으로 DTO의 역할을 다시 생각해보게 되었다.


원인 분석

두 DTO는 겉보기에는 비슷하지만

생성되는 위치와 방식이 완전히 다르다는 것을 확인할 수 있었다.

1. MenuResponse (일반 응답 DTO)

  • 엔티티 → DTO 변환 과정에서 생성
  • 서비스 레이어에서 직접 생성

이 경우 bulider를 사용하면

  • 필드명이 명확하게 드러나고
  • 순서에 의존하지 않아 안전하며
  • 확장에도 유연하게 대응할 수 있다.

따라서 builder가 적합하다.


2. PopularMenuResponse (조회용 DTO)

  • DB 조회 결과를 바로 DTO로 매핑
  • JPQL / QueryDSL에서 생성

예를 들어 JPQL에서는 아래와 같이 사용된다.

select new com.example.PopularMenuResponse(m.id, m.name, count(o.id))

 

이 방식은 생성자를 통해서만 동작하며,

builder 패턴은 사용할 수 없다.

 

따라서 조회용 DTO는 생성자를 사용하는 것이 자연스럽다.


해결 방법

DTO를 하나의 방식으로 통일하는 것이 아니라,

역할에 따라 생성 방식을 구분하기로 했다.

 

기준 정리

  • 일반 응답 DTO → builder 사용
  • 조회 결과 DTO → 생성자 사용

이 기준을 적용하니

 

  • 코드의 의도가 명확해지고
  • DTO의 역할이 더 분명해졌으며
  • 유지보수도 쉬워졌다

개선 결과

기존에는 DTO 생성 방식에 대한 기준이 없어
코드 작성 시마다 고민이 발생했지만,

 

명확한 기준을 정리한 이후에는

  • DTO를 만들 때 고민이 줄어들고
  • 상황에 맞는 생성 방식을 바로 선택할 수 있게 되었다.

또한 DTO를 단순한 데이터 객체가 아니라
설계 관점에서 바라보게 되는 계기가 되었다.


느낀 점

처음에는 사소한 차이라고 생각했던 부분이었지만,

정리해보니 DTO 역시 역할에 따라 설계가 필요한 요소라는 것을 느꼈다.

 

특히

"DTO는 그냥 데이터 담는 객체가 아니다"

라는 관점을 갖게 된 것이 가장 큰 수확이었다.

 

앞으로는 DTO를 작성할 때

  • 무조건 builder를 쓰거나
  • 무조건 생성자를 쓰기보다

해당 DTO가 어디에서, 어떻게 생성되는지를 먼저 고려하고

적절한 방식을 선택해야겠다고 생각했다.