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.