코틀린에서 유효성 검사를 적용할 때, @field: 사용 위치 지정자를 사용하는 이유와 그 차이를 명확히 이해하는 것은 중요합니다. 이는 실제 유효성 검사 시 예상한 대로 동작하도록 보장합니다.

코틀린 스프링에서 Validation 적용 방법과 주의점

1. 코틀린에서 Validation 애노테이션 적용 위치

코틀린에서 애노테이션을 프로퍼티에 적용할 때는 @field:, @get:, @set:, @param: 등의 사용 위치 지정자(use-site target)를 사용하지 않으면, 기본적으로 애노테이션은 프로퍼티의 게터 메서드에 적용됩니다.

예시:

import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Positive

data class CreatePostRequestDTO(
    val id: Long?,

    @field:NotBlank(message = "제목은 필수입니다.")
    val title: String,

    @field:NotBlank(message = "내용은 필수입니다.")
    val content: String,

    @field:Positive(message = "작성자는 양수여야 합니다.")
    val memberId: Long
)

2. @field:와 @get:의 차이

3. 테스트를 통한 유효성 검사 확인

아래는 필드와 게터 메서드에 각각 @NotBlank 애노테이션을 적용하여 유효성 검사를 테스트하는 예제입니다.

DTO 정의:

import jakarta.validation.constraints.NotBlank

data class FieldDTO(
    @field:NotBlank(message = "내용은 필수입니다.")
    val content: String
)

data class GetterDTO(
    @get:NotBlank(message = "내용은 필수입니다.")
    val content: String
)

테스트 코드:

package com.example.validation

import jakarta.validation.Validation
import jakarta.validation.Validator
import jakarta.validation.ValidatorFactory
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test

class ValidationTest {

    companion object {
        private lateinit var validator: Validator

        @BeforeAll
        @JvmStatic
        fun setUp() {
            val factory: ValidatorFactory = Validation.buildDefaultValidatorFactory()
            validator = factory.validator
        }
    }

    @Test
    fun `FieldDTO should fail validation when content is blank`() {
        val dto = FieldDTO(content = "")
        val violations = validator.validate(dto)
        assertEquals(1, violations.size)
        assertEquals("내용은 필수입니다.", violations.first().message)
    }

    @Test
    fun `GetterDTO should fail validation when content is blank`() {
        val dto = GetterDTO(content = "")
        val violations = validator.validate(dto)
        assertEquals(1, violations.size)
        assertEquals("내용은 필수입니다.", violations.first().message)
    }

    @Test
    fun `FieldDTO should pass validation when content is not blank`() {
        val dto = FieldDTO(content = "유효한 내용")
        val violations = validator.validate(dto)
        assertEquals(0, violations.size)
    }

    @Test
    fun `GetterDTO should pass validation when content is not blank`() {
        val dto = GetterDTO(content = "유효한 내용")
        val violations = validator.validate(dto)
        assertEquals(0, violations.size)
    }
}

4. 유효성 검사 동작 원리

코틀린은 널 안정성을 기본적으로 지원하지만, 클라이언트에서 들어오는 요청 데이터는 서버 측에서 유효성 검사가 필요합니다. DTO 클래스의 유효성 검사를 통해 데이터의 무결성을 보장할 수 있습니다.

5. @Valid를 사용한 컨트롤러에서의 유효성 검사

컨트롤러에서 @Valid를 사용하여 요청 바디의 객체에 대한 유효성 검사를 실행할 때는 필드에 직접 유효성 검사를 정의해야 합니다.