The Execute Around idiom is a pattern that allows you to wrap an action with some standard setup / tear down steps. Examples might include:
- Execute within a lock: acquire the lock, do the action, release the lock
- Resource handling: acquire the resource, do the action, close the resource
- Execute as a user: switch to the user, do the action, switch back to the original user
- Exception handling: do the action, handle the exceptions
- Time an action: start a timer, do the action, stop the timer
The pattern allows you to pass in arbitrary actions and have them run with the same setup / tear down steps.
Execute around idiom in Java
The idiom is well suited to languages supporting closures such as Javascript and Kotlin. However, we can implement this in Java 8+ using Lambda Expressions. The try-with-resources statement added to Java 7 is also a special case of Execute Around.
Hello world example
As a very simple example, here’s a program that prints to the console immediately before and after running an action.
class SimpleExecuteAround {
public static void main(String[] args) {
executeAround(() -> System.out.println("Doing action 1"));
executeAround(() -> System.out.println("Doing action 2"));
}
private static void executeAround(Runnable action) {
System.out.println("Do the setup");
action.run(); // do the action
System.out.println("Do the tear down");
}
}
The output of this program is
Do the setup
Doing action 1
Do the tear down
Do the setup
Doing action 2
Do the tear down
Retry on error
As a more realistic example, here’s the Execute Around idiom used to handle retries on error:
public class Retry<T> {
private final int retries;
public Retry(int retries) {
this.retries = retries;
}
public T retry(Supplier<T> action) throws NoRetriesRemainException {
Exception lastException = null;
for (int i = 0; i < retries; i++) {
try {
return action.get();
} catch (Exception e) {
System.out.println("Failed to execute action because of " + e + ". Retrying");
lastException = e;
}
}
// We've run out of retries.
throw new NoRetriesRemainException("Failed to execute action after " + retries + " attempts", lastException);
}
}
Note that in this case we pass our action in as Supplier with a generic type. This allows the execute around block to return a value from the action.
To use this code, we need some function that will sometimes fail. Here’s a method that will fail one time in ten.
private static int divideByRandom(int dividend) {
Random random = new Random();
int divisor = random.nextInt(10);
return dividend / divisor; // May produce ArithmeticException!
}
To show this in action, we repeatedly call divideByRandom from within the retry handler:
public static void main(String[] args) {
Retry<Integer> retry = new Retry<>(3);
for (int i = 0; i < 100; i++) {
try {
int result = retry.retry(() -> divideByRandom(100));
System.out.println("Result: " + result);
} catch (NoRetriesRemainException e) {
e.printStackTrace();
}
}
}
The result of this program is:
Result: 20
Result: 100
Result: 33
Failed to execute action because of java.lang.ArithmeticException: / by zero. Retrying
Result: 16
Failed to execute action because of java.lang.ArithmeticException: / by zero. Retrying
Result: 50
Result: 25
Result: 11
That is, when divideByRandom
returns a value, it’s printed. When divideByRandom
throws an Exception
, the retry handler tries again for us. This allows us to split the retry logic from he action we want to perform.
The great thing about the idiom is that we can reuse it in any context.
public static void retryAllTheThings() {
Retry<Void> retry = new Retry<>(3);
retry.retry(() -> sendARequestOverAnUnreliableNetwork());
retry.retry(() -> acquireALockThatMightAlreadyBeInUse());
retry.retry(() -> getAResourceThatMightNotBeReadyYet());
}
The retry pattern is great, many thanks