activity/인프런 워밍업 클럽

[인프런 워밍업 클럽 0기] 두 번째 과제 - API 만들기

dani0312 2024. 2. 20. 16:43

강의

첫 HTTP API 개발

 

`@GetMapping`과 `@PostMapping`을 이용해 GET API와 POST API와 같이 여러 API를  개발해보는 실습을 하였다. POST API의 경우 쿼리가 아닌 바디(body)를 사용하므로 POSTMAN에서 JSON언어로 데이터를 요청하고 응답 받는 것에 대해서도 학습하였다.

 


과제

우리는 GET API와 POST API를 만드는 방법을 배웠습니다. 👍 추가적인 API 들을 만들어 보며 API 개발에 익숙해져 봅시다!


📌API 개발

◾문제1

◾풀이

 🔻controller

@RestController
@RequestMapping("/api/v1")
public class assignController {

    // 두 수의 덧셈, 뺄셈, 곱셈 결과를 알려주는 API
    @GetMapping("/calc") // GET /api/v1/calc
    public CalResultResponse calculateTwoNumbers(CalculatorRequest request) {
        int addResult = request.getNum1() + request.getNum2();
        int minusResult = request.getNum1() - request.getNum2();
        int multiplyResult = request.getNum1() * request.getNum2();
        return new CalResultResponse(addResult, minusResult, multiplyResult);
    }
}

 

🔻dto

public class CalculatorRequest {
    private int num1;
    private int num2;

    public CalculatorRequest(int num1, int num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    public int getNum1() {
        return num1;
    }

    public int getNum2() {
        return num2;
    }
}

 

public class CalculatorResponse {
    private int add;
    private int minus;
    private int multiply;

    public CalculatorResponse(int add, int minus, int multiply) {
        this.add = add;
        this.minus = minus;
        this.multiply = multiply;
    }

    public int getAdd() {
        return add;
    }

    public int getMinus() {
        return minus;
    }

    public int getMultiply() {
        return multiply;
    }
}

 

 

⚠️시행착오

문제상황

GET요청을 보낼 시 두 개의 숫자의 값을 num1, num2로 CalculatorRequest라는 dto가 값을 받아와야하는데 값이 모두 0으로 출력되었다. 

 

원인

CalculatorRequest를 만들 시 필드와 getter만 선언하고 생성자를 선언해주지 않아서였다.

    public CalculatorRequest(int num1, int num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

이렇게 생성자를 선언해주어야 값이 정상적으로 이 객체에 들어가게된다.  

 

 

POSTMAN에서 요청 결과

 


◾문제2

 

 

◾풀이

🔻controller

    // 날짜를 입력 시, 몇 요일인지 알려주는 API
    @GetMapping("/day-of-the-week")
    public DayOfTheWeekResponse getDayOfTheWeek(DayOfTheWeekRequest request) {
        LocalDate localDate = LocalDate.parse(request.getDate());
        DayOfWeek dayOfWeek = localDate.getDayOfWeek();
        return new DayOfTheWeekResponse(dayOfWeek.toString().substring(0,3));
    }

 

🔻dto

public class DayOfTheWeekRequest {
    private String date;

    public DayOfTheWeekRequest(String date) {
        this.date = date;
    }

    public String getDate() {
        return date;
    }
}
public class DayOfTheWeekResponse {
    private String dayOfTheWeek;
    public DayOfTheWeekResponse(String dayOfTheWeek) {
        this.dayOfTheWeek = dayOfTheWeek;
    }
    public String getDayOfTheWeek() {
        return dayOfTheWeek;
    }
}

 

POSTMAN에서 요청 결과

 

🔨리팩토링

결과가 "MONDAY"와 같이 요일의 full String이 출력이 되었는데 이를 문제의 예제에 맞게 3글자만 출력되도록 변경하였다. String의 메서드 subString()을 이용하여 앞에서 3글자만 응답하도록 하였다.

return new DayOfTheWeekResponse(dayOfWeek.toString()); //before
return new DayOfTheWeekResponse(dayOfWeek.toString().substring(0,3)); //after

 

 

 


◾문제3

◾풀이

🔻controller

    // 여러 수를 받아 총 합을 반환하는 API
    @PostMapping("/sum")
    public NumbersResponse sumNumbers(@RequestBody NumbersRequest request) {
        int sum = 0;
        for (int i = 0; i < request.getNumbers().size(); i++) {
            sum += request.getNumbers().get(i);
        }
        return new NumbersResponse(sum);
    }

 

🔻dto

public class NumbersRequest {
    private List<Integer> numbers = new ArrayList<>();
    public NumbersRequest(){}

    public NumbersRequest(List<Integer> numbers) {
        this.numbers = numbers;
    }

    public List<Integer> getNumbers() {
        return numbers;
    }
}
public class NumbersResponse {
    private int sum;

    public NumbersResponse(int sum) {
        this.sum = sum;
    }

    public int getSum() {
        return sum;
    }
}

 

 

 

⚠️시행착오

문제상황

처음 postman에서 요청을 보냈을 때 500에러가 터지고, 서버에서는 아래와 같은 에러가 났다. (더보기 클릭)

더보기

ERROR 31188 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class cohttp://m.group.libraryapp.controller.assignment.dto.NumbersRequest]; nested exception is cohttp://m.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `cohttp://m.group.libraryapp.controller.assignment.dto.NumbersRequest` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

 

원인

NumberRequest에 있었는데 에러의 마지막 줄을 보면 '직렬화 할 수 없다. 기본 생성자같은 Creator가 없다' 의 내용이다. 이에 대해 알아보니 아래와 같은 두 가지 해결방법이 있다.

 

해결방법

방법1. dto에 `public NumbersRequest(){}` 빈 생성자를 추가한다. 

방법2. 생성자에 `@JsonCreator`를 붙인다. 매개변수에는 `@JsonProperty(numbers)`와 같은 것을 앞에 붙여준다.

    @JsonCreator
    public NumbersRequest(@JsonProperty("numbers") List<Integer> numbers) {
        this.numbers = numbers;
    }

 

 

🤔남아있는 의문

두 가지 방법 모두 실행해보니 실행은 잘 되었다. 그런데 한 가지 의문점은 그 전에 했던 request, reponse 모두 아무 문제가 없었는데 인자가 1개인 List타입인 이번 케이스에서는 왜 '객체를 역직렬화할 때 기본 생성자를 호출하여 객체를 생성' 해야 하는 것인지.. 이번 케이스에만 이런 경우가 해당하는 것인지 잘 모르겠다..

 

✔️의문 해결

질문을 해야겠다고 생각하였는데 커뮤니티에 같은 질문과 답변이 달려 해결되었다. 문제1,2(GET API)와 다른 API에서는 문제 없이 DTO가 데이터를 잘 받아왔는데 왜 문제3(POST API)에서는 기본 생성자가 필요한 것일까? 가 나의 의문이었다.

 

> GET은 쿼리, POST는 바디를 사용하여 데이터를 요청하는 차이가 있는데 이 점이 관련된 부분이었다. GET요청에서는 SpringBoot가 URL의 쿼리 매개변수를 사용하여 DTO를 채우기 때문에 기본 생성자가 필요하지 않다. 그러나 POST요청에서는 요청 본문을 객체로 역직렬화 할 때 기본 생성자가 필요하여, 없으며 오류가 발생한다. 

 

> 역직렬화 정책 상 기본 생성자가 있어야하는 것이었다. @PostMapping으로 넘어온 JSON 내부에 객체 형태로 넘어온 값을 실제 객체로 변환하기 위해 역직렬(deserialization) 과정을 거친다고 한다. 이때 역직렬화의 기본 정책이 "관련 클래스(DTO)에 Default constructor가 존재해야 한다"이기 때문이다. 따라서 Default constructor가 존재하지 않는 경우 스프링의 Jackson 라이브러리에서 InvalidDefinitionException 예외가 터진다.

 

내 의문이 감사하게 해결되었다

 

 

POSTMAN에서 요청 결과

 

 

 

 


Reference

자바와 스프링 부트로 생애 최초 서버 만들기 [서버 개발 올인원 패키지] / 최태현 / 인프런 강의