Backend/Spring

#8 GET API의 DTO와 POST API의 DTO가 차이가 나는 이유

RadderNepa 2023. 8. 9. 02:50

2023.07.30 - [Spring] - #5 GET API with DTO

 

#5 GET API with DTO

● GET API - 아래의 4가지는 API를 이루고 있는 요소이다. 이 4가지를 직접 정해서 GET API를 만들어보자 [API Specification(API 명세) = 어떻게 API를 만들 것인가] 1. HTTP Method → GET 2. HTTP Path → /add 3. 쿼리

radderveloper.tistory.com

2023.07.31 - [Spring] - #6 POST API

 

#6 POST API

● JSON(JavaScript Object Notation) - POST 방식에서는 HTTP Body를 이용해 데이터를 받는다. 이때 사용하는 문법이 바로 JSON이다. - Java에서 JSON과 비슷한 형식은 Map 이다.(Object인 만큼 다양한 형식의 값이 담

radderveloper.tistory.com

- 인프런에 나와 유사한 의문을 가지고 먼저 질문을 한 사람이 있어 정리해봤다.

- 따라서 이 글에서 정리한 내용은 강사의 답변을 기반으로 했다.

https://www.inflearn.com/questions/766418/%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94-5%EA%B0%95-6%EA%B0%95-%EA%B4%80%EB%A0%A8-%EC%A7%88%EB%AC%B8-%EB%82%A8%EA%B9%81%EB%8B%88%EB%8B%A4

 

안녕하세요. 5강, 6강 관련 질문 남깁니다. - 인프런 | 질문 & 답변

안녕하세요. 5강 6강을 수강하던 중 몇 가지 궁금증이 생겨 질문 남깁니다. 5강 GET API에서 사용된 CalculatorAddRequest -> 생성자 O, getter O6강 POST API에서 사용된 CalculatorMultiplyRequest -> ...

www.inflearn.com


● DTO 차이점

package com.group.libraryapp.controller.calculator;

import com.group.libraryapp.dto.calculator.request.CalculatorAddRequest;
import com.group.libraryapp.dto.calculator.request.CalculatorMultiplyRequest;
import org.springframework.web.bind.annotation.*;

@RestController
public class CalculatorController {
    @GetMapping("/add")
    public int addTwoNumbers(CalculatorAddRequest request) {
        return request.getNumber1() + request.getNumber2();
    }

    @PostMapping("/multiply")
    public int multiplyTwoNumbers(@RequestBody CalculatorMultiplyRequest request) {
        return request.getNumber1() * request.getNumber2();
    }
}

- #5번 글(GET API)의 DTO는 아래와 같다.

package com.group.libraryapp.dto.calculator.request;

public class CalculatorAddRequest {
    private final int number1;
    private final int number2;

    public CalculatorAddRequest(int number1, int number2) {
        this.number1 = number1;
        this.number2 = number2;
    }

    public int getNumber1() {
        return number1;
    }

    public int getNumber2() {
        return number2;
    }
    
    // final 키워드가 있으므로 어차피 setter는 생성할 수 없다.
}

- #6번 글(POST API)의 DTO는 아래와 같다.

package com.group.libraryapp.dto.calculator.request;

public class CalculatorMultiplyRequest {
    private int number1;
    private int number2;

    public int getNumber1() {
        return number1;
    }

    public int getNumber2() {
        return number2;
    }
}

- 두 DTO의 차이점은 아래와 같다.

1. 필드에 final 키워드 유무  /  2. Constructor(생성자) 유무

1. 왜 GET API의 CalculatorAddRequest의 number1, number2 필드에만 final 키워드만 붙었는가?

- final 키워드 유무는 큰 상관이 없다. 즉, CalculatorAddRequest의 필드에도 final 키워드가 반드시 있을 필요는 없다.

- 실제로 final 키워드를 지우고 GET API를 호출해봤는데 final 키워드가 있을 때와 동일하게 문제없이 돌아갔다.

- 참고로 DTO는 최초 객체 생성 이후 필드값이 변경되면 안 되기에 final 키워드를 붙이는게 더 안전하다고 할 수 있다.

 

2. Constructor(생성자) 유무

- 실제로 GET API의 CalculatorAddRequest에서 Constructor를 지워보니 값이 0으로 바인딩됐다.

package com.group.libraryapp.dto.calculator.request;

public class CalculatorAddRequest {
    private int number1;
    private int number2;

//    public CalculatorAddRequest(int number1, int number2) {
//        this.number1 = number1;
//        this.number2 = number2;
//    }

    public int getNumber1() {
        return number1;
    }

    public int getNumber2() {
        return number2;
    }
    // final 키워드가 있으므로 어차피 setter는 생성할 수 없다.
}

Constructor가 없는 상태에서 GET API 호출, 값이 0으로 나온다.

- 하지만 POST API의 CalculatorMultiplyRequest는 Constructor가 없어도 정상적으로 결과가 나왔다.

package com.group.libraryapp.dto.calculator.request;

public class CalculatorMultiplyRequest {
    private int number1;
    private int number2;

    public int getNumber1() {
        return number1;
    }

    public int getNumber2() {
        return number2;
    }
}

Constructor가 없는 상태에서 POST API 호출, 값이 정상적으로 나온다.

[이유]
- GET API에서 사용되는 DTO는 POST API와 달리 생성자를 통해 값이 바인딩된다.
- 따라서 생성자가 없으면 GET API를 통해 들어온 쿼리가 바인딩 되는게 아니라 기본값인 0이 필드값으로 된다.
 cf) 지역 변수가 아닌 클래스 변수나 인스턴스 변수는 선언만해도 기본값이 자동으로 설정(자동 초기화)된다.

https://blog.naver.com/rakpink/222470397436

 

#24 변수의초기화, 멤버변수의 초기화

● 변수의초기화, 멤버변수의 초기화 https://www.youtube.com/watch?v=ayRKMT6x-ms ▶ 지역변수...

blog.naver.com

http://www.tcpschool.com/java/java_member_initBlock

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

3. POST API의 CalculatorMultiplyRequest는 왜 생성자가 없어도 값이 바인딩되는 걸까?

- @RequestBody annotation이 있기 때문이다.

- @RequestBody annotation을 붙이면 필드에 대한 바인딩이 getter를 통해 수행된다. 그렇기에 생성자가 없어도 사용자가 던지는 값이 필드에 바인딩되는 것이다.(@RequestBody가 있기에 바인딩 되는 로직이 GET API와 다르다.)

- 물론 생성자를 일부러 만들어도 필드 바인딩은 아무 문제없이 수행된다.

 

cf1)

- CalculatorMultiplyRequest DTO에 일부러 생성자를 만들어 보았다.

package com.group.libraryapp.dto.calculator.request;

public class CalculatorMultiplyRequest {
    private int number1;
    private int number2;

    public CalculatorMultiplyRequest(int number1, int number2) {
        this.number1 = number1;
        this.number2 = number2;
    }

    public int getNumber1() {
        return number1;
    }

    public int getNumber2() {
        return number2;
    }
}

- 그래도 문제 없이 API를 호출하고 결과값을 받을 수 있었다.

 

cf2)

- CalculatorMultiplyRequest에 생성자가 없는 상태에서 @RequestBody를 지운채로 POST API를 호출 해보면 GET API와 똑같이 0이 나올지 테스트해봤다.

- 테스트 결과 0이 나왔다. @RequestBody가 없는 상태에서 생성자를 통해 바인딩도 해주지 않으니 인스턴스 변수의 초기값(0)으로 계산이 된 것이다.

- 만약 @RequestBody가 없는 상태에서 생성자가 있었다면 제대로 계산이 됐을 것이다.(이렇게 테스트해보니 제대로 계산됐다.)