DEV Community

loading...
Cover image for Functional way of adding retry logic to a java method

Functional way of adding retry logic to a java method

guntaka
Guntaka
・2 min read

Hello, today I want to demonstrate how you can enhance a legacy feature using functional thinking. I use Vavr for sample implementation. Note: Java 11 has some of those constructs, but my goal is run this it Java 8.

Problem description

Let's say you are using a library method to compute something. For some reason, it is failing more often than you expected in your environment. So your task is to enhance it to add few retries instead of failing on the first try. In essence your users are fine with delayed response but more probability of getting a result.

Sample Implementation of FunctionalRetry

import java.util.function.Consumer;
import java.util.function.Supplier;

import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.Stream;
import io.vavr.control.Option;
import io.vavr.control.Try;

public class FunctionalRetry {

  public static <T> Option<T> of(int maxTries, Supplier<T> theWorker, Consumer<Tuple2<Integer, Try<T>>> statusReporter) {
    return Stream.range(0, maxTries)
      .map(it -> Tuple.of(it, Try.ofSupplier(theWorker)))
      .peek(statusReporter)
      .find(result -> result._2.isSuccess()).map(t -> t._2.get());
  }

  public static <T> Option<T> of(int maxTries, Supplier<T> theWorker) {
    return of(maxTries, theWorker, input -> System.out.println("Iteration:" + input._1 + "> Result:" + input._2));
  }
}
Enter fullscreen mode Exit fullscreen mode

A class to demonstrate how to use the FunctionalRetry

import java.util.Random;
import io.vavr.control.Option;

public class Main {

  public static void main(String[] args) {
    Option<Integer> mayBe = FunctionalRetry.of(10, Main::doSomething);
    if (mayBe.isDefined()) System.out.println(mayBe.get());
  }

  //Samepl test function
  public static int doSomething() {
    if (new Random().nextInt(5) != 0) throw new NumberFormatException();
    return 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it. You can see the FunctionalRetry.of method that take the function, in this case it happens to be a supplier, number of retries, and optional watch function that report the status of each invocation to the caller.

This can be implemented with loops but that is going to add more code paths and extra complexity. But this implementation does not even care about the underlying method.

This code is also available on github gist
Inspirations for my post came from all people talking about functional programming, Paul Graham's Programming Bottom-Up is one of them.

Conclusion

In my opinion, compare to Imperative style, Functional style gives the programmer better control of where to put boundaries and who is responsible for what. Traditionally, we always say the developer who implemented the underlying method has to ensure all scenarios have been addressed before shipping, which is impossible in today's IT. So use functional style wherever you can to make you life easy as coder. Have fun and keep learning.

Note

I know the style and readability is not the best, so I am going to share the same implementation using Kotlin next time.

Discussion (0)