takeda_san’s blog

KotlinとVRを頑張っていく方向。

SpringをKotlinで書くとバリデーションつらい

きっかけ

ズバリこの話なんだけれども。

【Kotlin】KotlinでJava EEのBean Validationを使うときの注意点 - B-Teck!

こんな感じのControllerとRequest用のエンティティがあって

コントローラー

@RestController
@RequestMapping("address")
class AddressController {
    @PostMapping
    fun postAddress(@RequestBody @Validated addressRequest: AddressRequest, bindingResult: BindingResult): String {
        if (bindingResult.hasErrors()) return "validate error: ${bindingResult.allErrors}"

        // 型はnullableだけどnullが来ないという知識がRequest外に出ちゃうとKotlinにした意味がない
        addressRequest.tags!!.forEach {
            println("tag: $it")
        }

        return addressRequest.toString()
    }
}

エンティティ

data class AddressRequest(
        @field:NotNull(message = "addressId is null")
        val addressId: Int?,

        @field:NotBlank(message = "address is null or empty")
        val address: String?,

        @field:NotNull(message = "tags is null")
        val tags: List<String>?
)

見るからにつらそうですよね。 NotNullでバリデーションしてるのに肝心な型はnullableっていう…

@field:NotNull(message = "tags is null")
private val tags: List<String>?

tagsはnullableな型だけど、バリデーションしてるからnullは来ないよ!ということです。
うーん、Javaを彷彿とさせる感じですね。Koltinの良さが生かせてないです。

ゆく先々でこんなつらいコードを書かねばならんのか?
時にはnullガードをしつつ。

addressRequest.tags!!.forEach {
    println("tag: $it")
}

このnullは来ないよ知識がcontrollerぐらいの流失ならいいんですけど、この先の別処理の別クラスまで漏れちゃうとnull地獄の始まりですね…
というわけで、足りない頭で考えた結果をメモ。

privateにして、外部公開用のプロパティを用意する

ちょっと回りくどいけど、初期値が決まっているものはエルビス演算子で初期値を入れてnot nullな型に変換しちゃおうという作戦。

data class AddressRequest(
        @field:NotNull(message = "addressId is null")
        val addressId: Int?,

        @field:NotBlank(message = "address is null or empty")
        private val address: String?,

        @field:NotNull(message = "tags is null")
        private val tags: List<String>?
) {
    // 公開用
    val validatedAddress: String
        get() = address ?: ""

    val validatedTags: List<String>
        get() = tags ?: emptyList()
}

ちょっと初期値が決まらなそうな、 addressId 以外はnullを忘れられてハッピーですね。
問題はちょっと変数名を考えるのがめんどくさいということだけです。