Validation Data Class Parameters Kotlin

Viewed 10183

If I am modeling my value objects using Kotlin data classes what is the best way to handle validation. Seems like the init block is the only logical place since it executes after the primary constructor.

data class EmailAddress(val address: String) {

    init {
        if (address.isEmpty() || !address.matches(Regex("^[a-zA-Z0-9]+@[a-zA-Z0-9]+(.[a-zA-Z]{2,})$"))) {
            throw IllegalArgumentException("${address} is not a valid email address")
        }
    }
}

Using JSR-303 Example

The downside to this is it requires load time weaving

@Configurable
data class EmailAddress(@Email val address: String) {

    @Autowired
    lateinit var validator: Validator

    init {
        validator.validate(this)
    }
}
4 Answers

I did make a comment, but I thought I would share my approach to validation instead.

First, I think it is a mistake to perform validation on instantiation. This will make the boundary between deserialization and handing over to your controllers messy. Also, to me, if you are sticking to a clean architecture, validation is part of your core logic, and you should ensure with tests on your core logic that it is happening.

So, to let me tackle this how I wish, I first define my own core validation api. Pure kotlin. No frameworks or libraries. Keep it clean.

interface Validatable {

    /**
     * @throws [ValidationErrorException]
     */
    fun validate()
}


class ValidationErrorException(
        val errors: List<ValidationError>
) : Exception() {

    /***
     * Convenience method for getting a data object from the Exception.
     */
    fun toValidationErrors() = ValidationErrors(errors)
}

/**
 * Data object to represent the data of an Exception. Convenient for serialization.
 */
data class ValidationErrors(
        val errors : List<ValidationError>
)

data class ValidationError(
        val path: String,
        val message: String
)

Then I have a framework specific implementations. For example a javax.validation.Validation implementation:

open class ValidatableJavax : Validatable {

    companion object {
        val validator = Validation.buildDefaultValidatorFactory().validator!!
    }

    override fun validate() {
        val violations = validator.validate(this)
        val errors = violations.map {
            ValidationError(it.propertyPath.toString(), it.message)
        }.toMutableList()
        if (errors.isNotEmpty()) {
            throw ValidationErrorException(errors = errors)
        }
    }
}

The only problem with this, is that the javax annotations don't play so well with kotlin data objects - but here is an example of a class with validation:

import javax.validation.constraints.Positive

class MyObject(
        myNumber: BigDecimal
) : ValidatableJavax() {

    @get:Positive(message = "Must be positive")
    val myNumber: BigDecimal = myNumber

}

It seems unreasonable to me to have object creation validation anywhere else but in the class constructor. This is the place responsible for the creation, so that is the place where the rules which define what is and isn't a valid instance should be. From a maintenance perspective it also makes sense to me as it would be the place where I would look for such rules if I had to guess.

Actually, it looks like that validation is not a responsibility of data classes. data tells for itself — it's used for data storage.

  1. So if you would like to validate data class, it will make perfect sense to set @get: validation on arguments of the constructor and validate outside of data class in class, responsible for construction.

  2. Your second option is not to use data class, just use simple class and implement whole logic in the constructor passing validator there

  3. Also, if you use Spring Framework — you can make this class Bean with prototype scope, but chances are it will be absolutely uncomfortable to work with such kind of spaghetti-code :)

I disagree with your following statement :

Seems like the init block is the only logical place since it executes after the primary constructor.

Validation should not be done at construction time, because sometimes, you need to have intermediate steps before getting a valid object, and it does not work well with Spring MVC for example.

Maybe use a specific interface (like suggested in previous answer) with a method dedicated to executing validation.

For the validation framework, I personnaly use valiktor, as I found it a lot less cumbersome that JSR-303

Related