Default value of Date type in @RequestParam

Jaychy·2021년 3월 9일
0

프로젝트 PICK

목록 보기
5/8
post-thumbnail

본 글은 글쓴이의 개인적인 생각이 담겨있을 수 있습니다.

PICK 프로젝트 - pick-server-Saturn
https://github.com/DSM-PICK/pick-server-Saturn

사건의 발단

프로젝트 PICK에 새로운 기능이 추가되었다.
추가된 기능은 통계 기능인데 사실 이름이 통계이지,
사실상 과거의 출석 기록을 보여주는 기능이기 때문에
기존의 출석 현황을 보여주는 API에 날짜를 설정할 수 있도록 변경하면 될 것 같았다.

출석 현황 API는 Http Get Method이기에 날짜를 받을 방법은
Request Parameter 또는 Path Variable 밖에 없었다.
그래서 다음과 같은 이유로 Request Parameter를 선택했다.

  • 이미 Path Variable로 사용되고 있는 값들이 많았다.
  • REST에 맞게 설계하기 위해서는 기존의 URI을 변경하는 것이 옳아보였는데
    URI를 변경하게 되면 프론트의 코드도 변경해야 한다.

문제

다음은 기존의 출석 현황 API의 컨트롤러 메소드이다.

    @GetMapping("/student-state/{schedule}/{floor}/{priority}")
    fun showAttendance(
        @RequestHeader("Authorization") token: String,
        @PathVariable("schedule") schedule: Schedule,
        @PathVariable("floor") floor: Floor,
        @PathVariable("priority") priority: Int,
    ): AttendanceResponse {
        authService.validateToken(token)
        return attendanceService.showAttendance(schedule, floor, priority)
    }

여기에 날짜를 추가하면 다음과 같이 변경된다. (편의상 생성자만 확인한다.)

    fun showAttendance(
        @RequestHeader("Authorization") token: String,
        @PathVariable("schedule") schedule: Schedule,
        @PathVariable("floor") floor: Floor,
        @PathVariable("priority") priority: Int,
        @RequestParam("date") date: LocalDate,
    )

그리고 기존의 출석 현황을 가져오는 API를 사용할 수 있어야 하므로
date를 파라미터로 주지 않았을 때 디폴트로 현재 날짜가 들어가야 한다.
그래서 나는 다음과 같이 변경하였다.

@RequestParam("date") date: LocalDate = LocalDate.now()

그런데 문제가 해결되었으리라고 믿고 푸시를 했는데 CI가 실패한 것이다.

해결

문제의 원인은 컨트롤러 메소드의 생성자로 들어오는 값들은 디폴트 값이 적용되지 않는다는 것이다.
따라서 문제를 해결하려면 디폴트 값을 사용하지 않고 디폴트 값을 넣어야 한다는 것이다.

컨트롤러 메소드의 생성자로 들어오는 값에 디폴트 값이 적용되지 않는 이유는
자바에서는 디폴트 값을 사용할 수 없지만 코틀린에서는 디폴트 값을 사용할 수 있기 때문이다.
본 글에서는 @Jvm 관련 어노테이션을 사용하지 않았지만 자바와의 호환성을 위해
어노테이션을 붙이는 방법도 있다.
그러나 어노테이션을 이용한 방법은 인위적이고 예쁘지 않아서 다른 방법을 선택했다.

사실 간단하게는 다음과 같이 코드에서 null 체크를 하는 방법이 있다.

    @GetMapping("/student-state/{schedule}/{floor}/{priority}")
    fun showAttendance(
        @RequestHeader("Authorization") token: String,
        @PathVariable("schedule") schedule: Schedule,
        @PathVariable("floor") floor: Floor,
        @PathVariable("priority") priority: Int,
        @RequestParam("date") date: LocalDate,
    ): AttendanceResponse {
        authService.validateToken(token)
        return attendanceService.showAttendance(
        	schedule = schedule,
            floor = floor,
            priority = priority,
            date = if (date == null) LocalDate.now() else date,
        )
    }

하지만 코드를 보고 분명히 이 방법보단 예쁘고 깔끔하게 짤 수 있는 방법이 있다고 확신했다.
얼마 되지 않아 @RequestParam 어노테이션에 defaultValue 속성이 존재한다는 것을 알게 됐다.

@RequestParam("date", defaultValue = LocalDate.now()) date: LocalDate

이런식으로 작성하면 IntelliJ에서는
An annotation argument must be a compile-time constant라고
오류를 발생시키기 때문에 안된다는 것을 확인하였다.

이건 나중에 확인한 것인데 IntelliJ에서는 컴파일 타임에 상수인 것이 들어가야 한다고 말하지만,
애초에 defaultValue 속성의 타입이 String이었기 때문에 안되는 것이었다.

그렇게 해매다가 StackOverflow에서 해답을 찾을 수 있었다.

How to set the default value to LocalDateTime in @RequestParam - StackOverflow
https://stackoverflow.com/questions/58527992/how-to-set-the-default-value-to-localdatetime-in-requestparam

여기서는 defaultValue에 #{T(java.time.LocalDate).now()}를 작성하라고 소개해준다.
위 StackOverflow 글에서는 SpEL 공식문서도 소개해준다.

Spring Expression Language (SpEL)
https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html

공식문서를 읽어보니 ExpressionParser와 Expression 객체를 이용해
코드상에서도 SpEL을 사용할 수 있고,
어노테이션(@Value, @RequestParam 등)이나 XML과 같이
정적으로 작성되어야 하는 곳에 사용할 수 있는 것 같다.

대신 위에서 LocalDate를 사용한 것처럼 정적인 상태이므로 패키지명까지 모두 작성해주어야 한다.
그래서 결론적으로 다음과 같이 컨트롤러 메소드가 완성되엇다.

    @GetMapping("/student-state/{schedule}/{floor}/{priority}")
    fun showAttendance(
        @RequestHeader("Authorization") token: String,
        @PathVariable("schedule") schedule: Schedule,
        @PathVariable("floor") floor: Floor,
        @PathVariable("priority") priority: Int,
        @RequestParam("date", defaultValue = "#{T(java.time.LocalDate).now()}") date: LocalDate,
    ): AttendanceResponse {
        authService.validateToken(token)
        return attendanceService.showAttendance(schedule, floor, priority, date)
    }

문제가 생기면 마지막엔 결국 StackOverflow에 가게 되는 것 같다.

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

0개의 댓글