워밍업 클럽 BE-0기 2일차 과제
오늘은 개념적인 것들을 주로 알아보는 시간이었던 1일차와는 달리 직접 문제에 적인 예제 코드를 작성하는 시간이었습니다.
첫 번째 문제는 다음과 같습니다.
문제 1 : 두 수를 입력하고, 다음과 같은 결과가 나오는 GET
API를 만들어보자!
path:
/api/v1/calc
쿼리 파라미터 :
num1
,num2
{
"add" : 덧셈결과,
"minus" : 뺄셈결과,
"multiply": 곱셈결과
}
쿼리 파라미터로 값을 넘기므로, 최종적으로 요청은 localhost:8080/api/v1/calc?num1=X&num2=Y
와 같이
나올 것 입니다. 방법은 여러가지가 있습니다. @RequestParam을 사용할 수도 있고 DTO 객체로 파라미터를 받아서 해결할 수 있습니다. 제가 쓴 코드는 다음과 같습니다.
code
@RestController
public class AssignmentTwoProblemOneController {
@GetMapping("/api/v1/calc")
public CalculateResponse CalculateTwoNumbers(CalculateRequest request) {
int add = request.getNumber1() + request.getNumber2();
int minus = request.getNumber1() - request.getNumber2();
int multiply = request.getNumber1() * request.getNumber2();
return new CalculateResponse(add, minus, multiply);
}
}
저는 DTO 객체로 number1과 number2를 받았고 이를 응답하는 CalculateResponse 객체에 담아서 응답했습니다.
그리고 이렇게 요청을 보내면
이렇게 결과를 잘 받아올 수 있는 것을 확인할 수 있습니다.
하지만 이렇게 DTO 객체로 파라미터를 받지 않고 @RequestParam
을 사용해서 받을 수 있습니다. 그리고 저렇게 DTO 객체
를 받는 경우엔 객체 앞에 @ModelAttribute
를 선언해주는 것이 좋다고 합니다. 선언해주고 선언해주지 않고에 따른 동작의 차이가 큰 것은 아니지만, 코드를 읽는 사람으로 하여금 파라미터를 받는 메서드라는 것을 명확하게 알려주기 때문입니다. 또한 @ModelAttribute를 사용하면 좀 더 세밀하게 데이터 바인딩을 할 수 있습니다.
문제 2 : 날짜를 입력하면, 몇 요일인지 알려주는 GET
API를 만들어 보자!
path: /api/v1/day-of-the-week?date=2023-01-01
{
"dayOfTheWeek" : "MON"
}
우선 이 문제는 '시간'을 다루지는 않으므로 LocalDateTime
이나 LocalTime
이 아닌 LocalDate
를 사용해야 합니다.
이 문제에서는 처음에 해당 요청을 처리할 메서드에 선언한 LocalDate
타입의 변수가 Spring의 강력한 ArgumentResolver
가 알아서 변환해주지 않을까....? 싶었지만 어림도 없었습니다. LocalDate에 대해서 자세히 알아보라는 코치님의 조언이 있었는데 마침 내일 과제가 자바 8에 추가된 내용들을 공부하는 시간이므로 내일 다뤄보도록 하겠습니다.
@RestController
public class AssignmentTwoProblemTwoController {
@GetMapping("/api/v1/day-of-week")
public DayOfWeekResponse letMeKnowDayOfWeek(String date) {
LocalDate parsed = LocalDate.parse(date);
return new DayOfWeekResponse(String.valueOf(parsed.getDayOfWeek()).substring(0, 3));
}
}
우선 파라미터는 ISO-8601 형식의 문자열로 넘어옵니다. ISO-8601은 날짜와 시간 정보를 표현하는 국제 표준 형식입니다. 이 표준에서 날짜는 '연-월-일' 순서로 나타내집니다. 자바의 LocalDate
클래스는 이를 따르므로 파싱을 의미하는 parsed 메서드를 사용하면 문자열로 넘어온 '연-월-일'을 LocalDate
객체로 자동으로 바꿔줍니다.
(LocalDate
클래스에 들어가보면 ISO-8601을 따른다고 적혀 있습니다.)
그리고 그렇게 넘어온 LocalDate
타입의 객체에서 사용할 수 있는 getDayOfWeek()
메서드를 사용하면 해당 날짜의 요일을 Enum 타입으로 반환합니다. 문제 요구사항에서는 요일의 3번째 글자까지 반환할 것을 요구하고 있으므로, 문자열로 변환한 후에 substring
메서드를 사용했습니다.
그리고 오늘 날짜로 요청을 보내고 나면!
이렇게 결과가 잘 나오는 것을 알 수 있습니다.
문제 3 : 여러 수의 총 합을 반환하는 POST API를 만들어 보자!
path : /api/v1/calc
{
"numbers" : [1, 2, 3, 4, 5]
}
이 문제는 좀 반가웠습니다. 사실 그 동안 API를 만들면서 리스트를 입력으로 받아 본 적이 많지는 않아요. 늘 리스트는 레포지토리에서 뽑아서만 쓰곤 했습니다. 그래서 토이 프로젝트를 시작한다면 리스트를 입력으로 받는 기능들을 한번 고민해봐야겠습니다.
public class Numbers {
private List<Integer> numbers = new ArrayList<>();
public List<Integer> getNumbers() {
return numbers;
}
}
리스트를 받을 수 있게 List 타입의 변수를 선언하고 ArrayList를 선언했습니다. 조금 다른 소리이긴한데 항상 저렇게 List 타입의 변수를 선언하고 초기화를 할 때면, 늘 ArrayList를 쓰곤 했는데요. 여러분은 LinkedList나 다른 구현체를 선언해보신적이 있으신가요? 저는 없습니다. 그런데 괜찮다고 합니다. 흔히 자료구조를 공부할 때 배열의 특성을 가진 ArrayList의 경우 중간 삽입, 삭제가 많이 일어날 경우 자료들의 위치를 계속해서 이동 시켜줘야한다는 이유로 비효율적이라고 배웠지만 사실 자바에선 성능상의 큰 차이가 없다고합니다. 자바의 컬렉션 프레임워크를 만드는데 크게 일조한, 이펙티브 자바의 저자로도 유명한 조슈아 블로크도 본인의 트위터에
"내가 만들었긴 했는데 나도 잘 안쓴다."
라고 했다고 합니다. 기계적으로 ArrayList를 쓰는 것에 반성을 크게 하려했지만 이내 안심했습니다. 아무튼 저렇게 선언하고 컨트롤러에서는 다음과 같이 처리했습니다.
@RestController
public class AssignmentTwoProblemThreeController {
@PostMapping("/api/v1/calc")
public Integer calculateVariousNumber(@RequestBody Numbers numbers) {
List<Integer> numbersList = numbers.getNumbers();
return numbersList.stream().mapToInt(Integer::intValue).sum();
}
}
다른 문제에서 URL의 쿼리 파라미터로 요청을 받은 것과는 달리, 이 문제는 HTTP Body에 데이터를 담아서 보냅니다. 따라서 이를 핸들링해줄 수 있는 @RequestBody
를 사용했습니다. API를 만들어보신 분이라면 자주 사용하고 많이 봤던 어노테이션일 것입니다.
이 어노테이션을 사용해 HTTP Body로 들어온 입력값들을 가져온 후, For문으로 합계를 구할 수도 있겠지만 람다식을 이용해서 처리했습니다. 기본적으로 스트림에는 매핑용 중간 연산 메소드로 map을 제공하고 반환하는 형태에 따라 mapToInt
, mapToLong
, mapToDouble
이 있습니다. mapToInt
함수는 IntStream
을 반환하기로 정해져있고 IntStream
에는 총합을 계산하는 sum()
메서드를 제공합니다. 이는 최종 연산을 수행하는 메서드로서, 스트림은 중간 연산의 결과는 사용할 수 없다는 점을 참고해주세요.
for문을 사용하지 않아 줄이 많이 줄어들었고, 한 줄에 짧게 끝낼 수 있었습니다. 그리고 요청을 보내보면,
이렇게 결과를 잘 받을 수 있었습니다.
다른 분들은 과제를 하면서 궁금한 것들을 모두 해결하시던데, 저는 아직 거기까지 파다간 과제를 제때 제출하지 못할 것이 걱정되는 스케줄이기에.. 과제를 수행하면서 더 깊이 파봐야할 것들을 적어놓고 마무리하고자 합니다.
@RequestBody의 동작 원리와 Jackson 라이브러리
왜 @ModelAttribute나 @RequestParam 같은 것이 없고 그냥 POJO임에도 불구하고 파라미터를 자동으로 잘 매핑해줄까?
이상 글 마치겠습니다. 모든 워밍업 클럽에 참여하시는 분들의 성공과 상쾌한 아침을 기원합니다.
댓글을 작성해보세요.