공부함

dto를 사용해야 하는 이유 본문

스프링

dto를 사용해야 하는 이유

찌땀 2024. 9. 1. 20:13

작년에 사이드프로젝트를 진행하면서 api서버를 개발했다. 프론트에서 http 요청 바디에 데이터를 넘겨주면 api서버에서는 무조건 dto로 받기로 했다. 팀원이 그렇게 처음부터 정하고 프로젝트를 진행했기 때문에 왜 dto를 사용해야 되는지 고민해보지를 않았다. 

dto를 쓰지 않은 코드를 보면서 역체감으로? 왜 dto를 쓰는게 좋은 지 느끼게 된 바가 있어 글을 작성해보겠습니다. 어디까지나 제 주관적인 생각입니다. 

 

HttpServletRequest getParameter 사용

    @RequestMapping("/messages")
    public ModelAndView make(HttpServletRequest request) {
        
        String title = request.getParameter("title");
        String vGrade = request.getParameter("vGrade");-
        String name = request.getParameter("name");
        String age = request.getParameter("age");

        // 비즈니스 로직 ..

        ModelAndView mv = new ModelAndView("/messageView.jsp");
        return mv;
    }

Http 요청을 통해 데이터를 전송하는 방법은 크게 3가지가 있습니다.

  1. url 쿼리파라미터(혹은 쿼리스트링)
  2. post 요청의 form data
  3. 요청 body의 데이터 

1, 2번은 HttpServletRequest의 getParameter로 바로 값을 얻을 수 있다. 

(3번은 @RequestBody + dto로 받는 편이 깔끔하다)

이런 방식을 사용할 경우 몇가지 단점이 있다. 

 

1. 파라미터 이름으로 값의 의미를 파악하기 힘들다. 

title, age, name 같은 직관적인 이름은 괜찮지만 , vGrade같은 이름은 코드를 처음 보는 사람이 이해하기 힘들다. 

2. 어떤 방식으로 데이터가 넘어왔는지 구분할 수 없다. 

vGrade가 어떤 값인지 궁금해서 /messages로 요청을 보내는 view 파일을 찾아볼 수 있다. 간단한 파일이라면 상관 없지만, view가 복잡하면 찾는 데 시간이 오래 걸린다. 심지어 form data인지 쿼리스트링인지 모르기 때문에 form 부분, 요청을 보내는 부분의 url을 모두 확인해야 할 수도 있다. 

3. 형변환 과정이 필요하다. 

age는 나이이므로 int값으로 사용할 것이지만 getParameter로 받으면 String 타입이라 형변환이 필요하다. 

(getAttribute 메서드를 사용하면 Object 타입으로 반환받을 수 있긴 하다)

4. 중복이 발생한다.

컨트롤러마다 getParameter로 값을 가져오는 과정이 중복되어 발생한다. 

 

@RequestParam 사용 

    @RequestMapping("/messagesV2")
    public ModelAndView makeV2(@RequestParam String title, @RequestParam(required = true, defaultValue = "19") int age,
                               @RequestParam String vGrade, @RequestParam String name) {

        // 비즈니스 로직 ..

        ModelAndView mv = new ModelAndView("/messageView.jsp");
        return mv;
    }

@RequestParam 애노테이션을 사용할 수 있다. 

 

getParameter의 중복이 해소되고, 형변환 과정도 사라졌다.

추가로 required, defaultValue같은 제한도 둘 수 있어서 유용하다.  

하지만 여전히 vGrade가 무엇인지 알기는 어렵고, 쿼리스트링인지 form data인지 알 수 없다. 

 

dto + @ModelAttribute 사용

// Controller
@RequestMapping("/messagesV3")
public ModelAndView makeV3(@ModelAttribute MessageMakeRequest messageMakeRequest,
                           @ModelAttribute MemberInfoRequest memberInfoRequest,
                           @RequestParam int page) {

    // 비즈니스 로직 ..

    ModelAndView mv = new ModelAndView("/messageView.jsp");
    return mv;
}
    
//dto
@Getter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class MemberInfoRequest {
    private final int age;
    @NotBlank(message = "NAME 값이 BLANK 입니다")
    private final String name;
}

@Getter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class MessageMakeRequest {
    private final String title;
    private final String vGrade;
}

dto를 만들고 @ModelAttribute를 사용할 수 있다. 

 

dto를 사용하면 vGrade가 MessageMakeRequest의 필드이기 때문에 메세지를 등록할 때 필요한 값 중 하나라는 것을 쉽게 이해할 수 있다. @RequestParam만 사용한 컨트롤러를 보면 vGrade가 무엇을 위한 값인지 알기 어렵다. 

 

사실 @ModelAttribute와 @RequestParam 모두 쿼리스트링 또는 html form 방식 둘 다 인식한다. 즉 @ModelAttribute로 dto를 사용한다고 해서 이 값은 form으로 넘어왔다고 할 순 없다. 하지만 "쿼리스트링인 경우 @RequestParam을, form data인 경우 관련있는 값 끼리 dto로 묶어서 @ModelAttribute를 사용하자" 와 같은 약속을 한다면 애노테이션을 보고 넘어온 방식을 알 수 있다. 이건 사실 쓰고 보니 좀 억지같다.. 

 

추가로 Bean Validation을 사용해 간편하게 검증할 수 있다. 예외메세지도 애노테이션 속성값으로 적을 수 있고 예외도 묶어서 편하게 처리할 수 있어서 편리하다. 

 

결론 

사실 별 대단한 내용은 아니지만, 내가 실제로 느낀 점을 토대로 글을 작성하니 뿌듯하다.

개인적인 취향으로는 관련있는 값은 @ModelAttribute로 dto로 묶어서 받고, 쿼리스트링은 @RequestParam으로 받는 편이 깔끔한 것 같다.