Tactical Design by Example - Using Kotlin and Spring Boot (Part 11) - Improving Command factories with Kotlin's Result type

Only recently have I come across the Result type in Kotlin's standard library. It's been added in version 1.3. It's a great way to implement a simple Either-like type, something I have mentioned in the previous week.

Return nullable type or Result?

I built a factory method for a command that returned a nullable type

data class UpdateDepositPrice(
    val articleId: String,
    val refundPrice: Int,
    val currency: Currency,
    val currencyUnit: CurrencyUnit
) : Command() {
    companion object {
        operator fun invoke(priceUpdated: PriceUpdated): UpdateDepositPrice? =
            priceUpdated.getArticleId()?.let { articleId ->
                UpdateDepositPrice(
                    articleId,
                    priceUpdated.price.customAttributes.refund,
                    Currency.valueOf(priceUpdated.price.currency),
                    CurrencyUnit.valueOf(priceUpdated.price.unit)
                )
            }
    }
}

Problem is, I now pass no error message back to the caller, and I have to log a generic error message.

UpdateDepositPrice(priceUpdated)?.also {
    workflow.process(it)
} ?: log.warn("generic error message")

Enter the Result type

Let's rewrite the factory method with the Result type

operator fun invoke(priceUpdated: PriceUpdated): Result<UpdateDepositPrice> =
    priceUpdated.getArticleId()?.let { articleId ->
        Result.success(
            UpdateDepositPrice(
                articleId,
                priceUpdated.price.customAttributes.refund,
                Currency.valueOf(priceUpdated.price.currency),
                CurrencyUnit.valueOf(priceUpdated.price.unit)
            )
        )
    } ?: Result.failure(
        IllegalArgumentException("No article id found in listing!")
    )

This adds a minimal wrapper around the returned command, plus an exception in case the articleId is null. The exception is not thrown!

Handling the result

There are various options here, I have tried the following

fold

Fold takes two lambdas as arguments. It is short, but the term "folding" is a bit too technical for my taste

UpdateDepositPrice(priceUpdated).fold({
    workflow.process(it)
}, { e ->
    log.warn(e.message)
})

onSuccess, onFailure

Even though this is a little longer, I think this is much more readable, especially if you are not aware of the Result type

UpdateDepositPrice(priceUpdated)
    .onSuccess {
        workflow.process(it)
    }.onFailure { e ->
        log.warn(e.message)
    }

Both are valid options. You can also go for getOrElse or getOrDefault, read more about the Result type in the Kotlin documentation.

One minor drawback though

Returning Result types is still an experimental feature, so use it at your own risk. You need to activate it via the compiler flag

-Xallow-result-return-type

Thanks to Pierre-Yves Saumont for mentioning the Result type in his book The Joy of Kotlin. Very helpful!

Go back to part ten.