Backend/Kotlin

[Kotlin] RequestBody 유효성 검증로직 추가하기

findmypiece 2022. 3. 7. 02:40
728x90

RequestBody에 할당되는 값에 대한 유효성 검증 방법에는 여러가지가 있을 것이다. Spring 환경이라면 대표적으로 @Valid 또는 @Validated 을 사용할텐데 여기에서는 직접 유효성 검증 로직을 추가하는 방법을 알아본다. 사실 이 방법은 Kotlin 에서만 국한된 것은 아니기 때문에 개념만 이해한다면 어디서든 사용할 수 있다.

 

 

우선 여기에서 @Valid와 @Validated 의 차이는 구체적으로 다룰 필요는 없겠지만 간단하게 말하면 그냥 @Validated 이 상위호환 이라고 보면 되고 아래부터 @Validated 로만 표현하겠다.

 

@Validated 는 왜 사용하지 않았나?

  • 단일 값의 유효성만 검증하는게 아니라 2개 이상 값의 조합도 검증하고 싶었다. 예를 들어 주민등록번호를 검증을 한다고 하면 단순히 주민등록번호가 유효한지 체크하는게 아니라 성별이 무엇인지에 따라 뒷부분 첫자리가 유효한지도 체크하고 싶었다. 
    (혹시 @Validated 로도 이게 가능하다면 제보 바란다.)
  • @Validated 는 기본적으로 특정 메소드에 할당되는 파라미터에 대해서만 유효성 검증을 수행할 수 있다. 즉, 수동으로 객체를 생성할 때는 유효성 검증이 동작하지 않는다. 하지만 나는 수동으로 객체를 생성할 때도 유효한 값만 제한하고 싶었다.

 

유효성 검증 로직 자체는 그냥 편한대로 만들면 되는데 객체 생성시 이걸 어떻게 적용하느냐가 문제일 것이다. 가장 단순한 방법으로는 생성자에 추가하면 되는데 일반 class 에서는 문제가 되지 않지만 data class 에는 이걸 적용하기가 좀 애매하다.

 

이유는 data class 의 경우 반드시 1개 이상의 인자를 포함한 주 생성자 정의가 강제되기 때문인데 코틀린에서 주생성자는 클래스 선언문에 포함되는 것으로 해당 생성자에는 별도의 로직을 추가할 수가 없다.

 

물론 별도의 부생성자를 생성해도 되지만 유효성 검증 로직이 포함되지 않은 주생성자로도 얼마든지 객체 생성이 가능하기 때문에 유효하지 않는 객체가 생성될 여지가 남아있게 된다. 유효성 검증 로직을 제대로 적용하려면 무조건 해당 로직을 타서 객체가 생성되도록 강제할 방법이 필요하다.

 

그래서 내가 생각한 방법은 팩토리 메소드 패턴을 활용하는 것이다. data class 선언시 주생성자를 private 으로 선언해서 외부에서 생성자를 통한 객체 생성을 제한하고 companion object 를 통해 외부에서 사용할 팩토리 메소드를 제공하는 방식이다. 그리고 유효성 검증 로직은 팩토리 메소드에 포함하도록 한다.

 

대략 아래와 같은 형태가 된다. 이를 통해 객체 생성은 of 메소드만으로 제한되기 때문에 객체 생성시 유효성 검증이 가능하다.

data class TargetApi private constructor(
    val countUri: String,
    val listUri: String,
    val pageParam: String,
    val limitParam: String
){
    companion object {
        @JsonCreator
        @JvmStatic
        fun of(
            countUri: String?,
            listUri: String?,
            pageParam: String?,
            limitParam: String?
        ): TargetApi {
            val targetApi = when {
                countUri.isNullOrBlank() -> throw InmsException("'countUrl' is required")
                listUri.isNullOrBlank() -> throw InmsException("'listUrl' is required")
                pageParam.isNullOrBlank() -> throw InmsException("'pageParam' is required")
                limitParam.isNullOrBlank() -> throw InmsException("'limitParam' is required")
                else -> TargetApi(
                    countUri,
                    listUri,
                    pageParam,
                    limitParam
                )
            }
            return targetApi
        }
    }
}

 

그렇다면 이걸 RequestBody 에는 어떻게 적용해야 할까? RequestBody 는 기본적으로 직렬화/역직렬화 과정이 필요하고 이때 가장 많이 사용되는 포맷은 json 이다.

 

Controller 영역에서는 json 으로 전달받은 파라미터를 역직렬화 하는 과정이 필요하고 이 단계에서 위에 정의한 of 메소드가 사용되게 하면 된다. 그 역할을 하는 것이 @JsonCreator 이다.

 

그렇다면 @JvmStatic 어노테이션은 필수는 아니지만 api 간 body 통신 규약이 json 이라면 반드시 이를 지정해야 역직렬화시 해당 메소드를 통하게 된다.

 

그렇다면 @JvmStatic 는 왜 필요한가? @JsonCreator 의 경우 생성자 또는 static 메소드에만 사용할 수 있는데 companion object 는 java의 static 메소드로 컴파일 되지 않기 때문에 명시적으로 @JvmStatic 어노테이션을 지정해서 static 메소드로 컴파일 되도록 해야 한다.

 

이를 통해 RequestBody 뿐만 아니라 수동으로 객체를 생성할 때도 동일한 유효성 검증을 적용할 수 있다.

728x90

'Backend > Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린에서 Mapstruct 사용하기  (0) 2022.04.05
[Kotlin] 로깅  (0) 2022.03.08
Coroutine timeout 설정  (0) 2022.02.04
[Kotlin] 코틀린에서 @QueryProjection 사용하기  (0) 2022.02.03
[Kotlin] JPA Entity. data class? class?  (0) 2022.02.03