4

Kotlin Retry to Make Your Code More Resilient | by Vlad Mykol | Medium | Better...

 1 year ago
source link: https://betterprogramming.pub/kotlin-retry-to-make-your-code-more-resilient-5b8cc4fac4e4
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin Retry To Make Your Code More Resilient

Kotlin wrapper function to Retry any part of a code

1*jQ61LN6iSPO2EHTSus9O4g.png

This is one of my “Kotlin cookbook” functions and its variations when I need a simple Retry applied to any part of my code. I did not find anything similar in standard Kotlin inline functions, so I tried to invent my own.

Simplest Implementation

It’s a common fact that the “Retry pattern” is considered best practice when working with Remote Resources and is a must for Resilience design. So when I need a simple but elegant retry, I bring this inline function to my source code:

inline fun <T> executeWithSocketTimeoutRetry(call: () -> T) =
try {
call()
} catch (e: ProcessingException) {
call()
}

Remember that in most of the WebClient implementations, you are not able to catch “java.net.SocketTimeoutException: Read timed”directly, and it’s a common mistake to do so

Instead, you need to catch a specific wrapper exception. Of course, you can catch general Exceptions, but this is not recommended.

Usage

val result = executeWithSocketTimeoutRetry {//do request and return a result
}

Here is how to wrap a part of a code with retry on SocketTimeoutException(Read timed out) thrown by the Jersey REST framework and get a result from it.

Improved Implementation

I think you’ve already got the idea, and to make things obvious, here is another more useful implementation of the above “Retry” function.

Here I declare a type of exception that I want to catch. This makes this function more reusable. Due to language limitations, I cannot directly catch parametrized exceptions. A workaround would be catching all exceptions and checking them with condition expression after.

Usage

executeWithRetry<IllegalArgumentException> {//some important code here
}

In this variant, I am retrying a part of a code and not expecting any output.

Advanced Implementation

But what if you need to return some value from a wrapped code? Well, it’s also possible, but it would be easier to move the exception type to a parameter list as Kotlin cannot automatically infer more than one generic type. Since we would have parameters now, let’s bring one more handy — the number of retries.

Usage

This is how we can simply retry any part of the code in Kotlin.

val result = executeWithRetry{
// code that returns value
}

or be more specific and retry only on IllegalArgumentException.

val result = executeWithRetry({ it is IllegalArgumentException }) {
// code that returns value
}

Using predicate gives us a lot of advantages in terms of flexibility. For example, we can catch some exception types while unwrapping the original one.

val result = executeWithRetry({ it is IllegalArgumentException || it.cause is IOException}) {
// code that returns value
}

We can even do additional actions after every retry, like logging retry occurrences with the also keyword.

val result = executeWithRetry({ e ->
(e is IOException).also { if (it) println("Retrying after $e") }
}) {
// code that returns value
}

Or even retry more than once with delays between retries.

val result = executeWithRetry({ e ->
(e is IOException).also { if (it) Thread.sleep(100) }
}, retries = 2) {
// code that returns value
}

Bonus Java Implementation

If you are interested in Java implementation, I’ve got you covered. Before Kotlin, I used the same idea of wrapping part of a code with additional logic since Java 8.

I am wrapping all Exceptions here with RuntimeExceptions for convenience.

var restult = executeWithRetry(IllegalArgumentException.class, () -> {
//code that returns result
});

Conclusion

This is how you can retry part of your code without any additional libraries or code dependencies. You also can use this wrapper idea for other features like logging, before/after logic, etc. Some standard Kotlin inline functions use the same approach, like measureTime, for example.

Of course, this is the most straightforward retry implementation. Once I need something more complex, I go with the Resilience4j library as it gives me other ready-to-use features like retry with ExponentialBackoff and CircuitBreaker.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK