Handling Null in URL
About
API를 구현하다보면 /posts?type=notice
의 ?type=notice
, /posts/{id}
, 에서 id
처럼 URL을 통해 값을 전달하는 경우가 있다.
Spring MVC에서는 컨트롤러에서 요청으로 들어온 해당 값들을 각각 @RequestParam
, @PathVariable
어노테이션을 이용해 참조할 수 있다.
@RequestParam
Java
Spring을 자바로 작성할 때는 다음과 같이 진행할 수 있었다.
type
은 실제로 null이 들어올 수도 있다. required=false
를 전달해서 필수값이 아님을 명시한다. (디폴트는 false
)
defaultValue
로 기본값을 전달할 수 있다. (""
로 감싸줘야 한다.)
Kotlin
코틀린에서는 언어 차원에서 타입 뒤에 ?
를 붙이는 것으로 nullable 타입과 non-nullable 타입을 구분하고 (완전히 다른 타입이 된다), 함수에서 디폴트 값을 지원한다. 디폴트 값은 이런 식으로 전달할 수 있다.
sentence
로 아무것도 전달되지 않으면 =
뒤의 디폴트 값이 들어가게 된다.
그렇다면 스프링에서도 이 기능을 응용해서 PostController
예제를 다음과 같이 작성하면 어떨까?
그러나 이렇게 하게 되면 실제로는 type
에는 null이 들어간다.
Spring의 ArgumentResolver
(RequestParamArgumentResolver
)를 거치며, type
에 애초에 null이 들어간 채로 시작하므로, 디폴트 값이 삽입되지 않는 것이다.
따라서 다음과 같이 진행하면 된다.
required = true
또 하나 짚고 넘어갈 부분이 있다. required
옵션에 관한 내용이다. required
와 defaultValue
유무의 조합으로 4가지가 나오는데, 각 상황에서 오류를 일부러 발생시켜보자. (500
이 나오면 원치 않는 상황이 되는거다!)
1번 상황 - defaultValue 없음, required=true, non-nullable
type
의 디폴트 값을 없애고, 반드시 전달받도록 하고 싶다. required=true
를 넣고 non-nullable 타입으로 하면 될까?
일부러 오류를 발생시키기 위해 이렇게 디폴트값을 안넣고 요청해보자.
자, 이렇게 전달하면 500이 아닌 400 Bad Request가 반환된다.
✅ 바람직하다! 나머지 상황도 계속해서 확인해보자.
2번 상황 - defaultValue 없음, required=false, nullable
type
을 전달하지 않으면 null이 들어가고, type
을 전달하면 해당 값으로 초기화된다. 둘 다 오류가 발생하지 않는다.
✅ 바람직하다.
3번 상황 - defaultValue 있음, required=true, non-nullable
type
을 전달하지 않으면 디폴트 값 "post"
가 들어가고, type
을 전달하면 해당 값으로 초기화된다. 둘 다 오류가 발생하지 않는다.
✅ 바람직하다.
4번 상황 - defaultValue 있음, required=false, nullable
3번 상황과 동일하게 동작한다. 다만 컨트롤러에서 해당 값을 사용할 때 null이 들어가지 않았음에도 불구하고 null 체크를 해줘야 한다.
🫤 불편하다.
잘 되는 예제를 알아봤으니 이제 required와 nullable을 다르게 만 혼종 케이스를 알아보자. (궁금하니까!)
5번 상황 - defaultValue 없음, required=true, nullable
4번 상황과 동일하게 동작하며, 해당 값을 사용할 때 null이 들어가지 않았음에도 null 체크를 해줘야 한다.
근데 문제가 있다. required=true
인데 type
을 전달하지 않아도 오류가 발생하지 않는다.
필수값으로 마킹하기 위해 required=true
로 설정한 것인데 필수값 체킹이 안된다니...
❌ 끔찍하다.
6번 상황 - defaultValue 없음, required=false, non-nullable
type
을 전달하면 문제 없이 동작한다. 그런데 전달하지 않았다면 다음과 같이 NullPointerException
이 발생한다.
그리고 500 Error
가 반환된다.
❌ 실수가 아니라면 이럴 일 없겠지만 당연히 이러면 안된다.
7번 상황 - defaultValue 있음, required=true, nullable
5번과 동일하게 동작하며 문제도 같다고 할 수 있. 디폴트값도 있고 required=true
인데 굳이 nullable 타입을 쓸 필요가 있을까?
🫤 바람직하지 않다.
8번 상황 - defaultValue 있음, required=false, non-nullable
defaultValue
가 있고, required=false
이다. type
은 non-nullable이다.
🤔 의도한 대로는 동작한다...만 defaultValue
만 빠뜨려도 6번이랑 똑같아진다. 주의해야겠다.
결론
defaultValue가 있는 경우엔 다음과 같이 하자.
required=true
(또는false
- 오류는 안나지만 주의 필)non-nullable (e.g.,
String
)
defaultValue가 없는 경우엔 다음과 같이 하자.
required=false
nullable (e.g.,
String?
)
@PathVariable
@PathVariable
에도 required
옵션으로 null checking을 진행할 수 있다.
Java
보통 @PathVariable
은 다음과 같이 사용한다.
사실 위의 예제를 활용해도 @PathVariable
을 optional하게 사용하긴 어렵다. /posts
로 요청하든, /posts/
로 요청하든, 해당 URL로 매핑된 컨트롤러를 찾지 못해 404 Not Found
가 반환될 것이다.
@PathVariable
에 required
옵션을 억지로 넣어보자면 다음과 같다.
@PathVariable
의 경우 defaultValue
는 지원하지 않는다.
Kotlin
Kotlin에서는 다음과 같이 할 수 있겠다.
참고로 required=true
, non-nullable
타입으로 바꿨을 때 당연히 id
를 전달하지 않으면 id를 전달하지 않는 경우의 코드를 실행시킬 수 없으므로 의미가 없을 뿐 아니라 500 Error
를 반환한다.
이런 경우엔 그냥 따로 컨트롤러 메소드를 하나 만들자.
More on the matter
오늘도 끄적끄적 - (Kotlin) Request에서 Nullable? Non-Null?
@RequestHeader
에 대해서도 소개한다.
REF
Yoon Sung's Blog - RequestParam, PathVariable 에 디폴트값 넣어주기
해당 블로그에서 모든 내용을 참고한다.
Last updated