How to add Resiliency to any APIs
Resiliency Handler
This simple generic class helps to add resiliency to any APIs with follow delay strategies
-
Exponential backoff and jitter strategy.
-
Fixed delay strategy.
ResiliencyHandler.java
package circuitbreaker;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* Generic ResiliencyHandler to simplify retry operations with fixed/exponential
* backoff delay strategies
*
* <pre>
* Student student =
* ResiliencyHandler.buildFor(Student.class)
* .usingJitterStrategy(3, 10L * 1000L)
* .tryWithResiliency(() -> studentService.getStudent())
* .fallbackOnFail(() -> studentService.getDefaultStudent())
* .getResult();// return result
*
* </pre>
*
* @param <R> ResultType
*
* @author ThirupathiReddy Vajjala
*/
@Slf4j
public class ResiliencyHandler<R> {
/**
* Expected Result
*/
private R result;
/**
* builder instance
*/
private Builder<R> builder;
/**
* @param builder builder
*/
private ResiliencyHandler(Builder<R> builder) {
this.builder = builder;
}
/**
* @param clazz clazz
* @param <R> ofType
*
* @return Builder
*/
public static <R> Builder<R> buildFor(Class<R> clazz) {
log.info("Creating builder instance for clazz {}", clazz);
return new Builder<>();
}
public static class ResiliencyException extends RuntimeException {
private String message;
public ResiliencyException(String message) {
super(message);
this.message = message;
}
public ResiliencyException(String message, Throwable throwable) {
super(message, throwable);
this.message = message;
}
public ResiliencyException(Throwable throwable) {
super(throwable);
this.message = throwable.getMessage();
}
@Override
public String getMessage() {
return message;
}
}
RuntimeException runtimeException;
/**
* Retry function
*
* @param supplier Supplier
*
* @return ResiliencyHandler
*/
public ResiliencyHandler<R> tryWithResiliency(Supplier<R> supplier) {
int counter = 0;
do {
try {
this.result = supplier.get();
builder.setSuccess(true);
return this;
} catch (Exception exception) {
log.debug("Failure occurred", exception);
log.warn("Operation failed due to {}", exception.getMessage());
this.runtimeException = new ResiliencyException(exception.getMessage(), exception);
}
if (counter++ == builder.getMaxAttempts()) {
break;
}
builder.setAttemptsMade(counter);
builder.waitForNextAttempt();
log.info("Retry Attempt: {}", counter);
} while (counter <= builder.getMaxAttempts());
return this;
}
/**
* Execute fallback behaviour if retry attempts fail
*
* @param supplier Supplier
*
* @return ResiliencyHandler
*/
public ResiliencyHandler<R> fallbackOnFail(Supplier<R> supplier) {
if (!builder.isSuccess()) {
log.info("Executing fallback behaviour");
this.result = supplier.get();
}
return this;
}
/**
* Return result onCompletion
*
* @return result
*/
public R getResultOrRethrow() {
if (null == result && null != runtimeException) {
throw runtimeException;
}
return result;
}
/**
* @param customException must be RuntimeException when you work with java8 lamda/functional interfaces
*
* @return result if available
*
* @throws RuntimeException {@link RuntimeException}
*/
public R getResultOrRethrow(RuntimeException customException) {
if (null == result && null != customException) {
throw customException;
}
return result;
}
/**
* Return result or default value
*
* @param defaultResult defaultResult
*
* @return result
*/
public R getResult(R defaultResult) {
if (null == result) {
return defaultResult;
}
return result;
}
public R getResult() {
return result;
}
/**
* Builder class
*
* @param <R>
*/
public static class Builder<R> {
/**
* flag to check retry success
*/
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
/**
* default max retry attempts= 3
*/
private int maxAttempts = 3;
public int getMaxAttempts() {
return maxAttempts;
}
/**
* Default fixedDelay after each retry (5 seconds)
*/
private long fixedDelayInMillis = TimeUnit.MILLISECONDS.toSeconds(5L * 1000L);
/**
* Default maximum delay with jitter config 25 seconds
*/
private long maxDelayInMillis = TimeUnit.MILLISECONDS.toSeconds(25L * 1000L);
/**
* Total attempts made (used runtime)
*/
private int attemptsMade;
/**
* Returns attempts made so far
*
* @return attempts made so far
*/
public int getAttemptsMade() {
return attemptsMade;
}
/**
* flag to check if jitterConfiguration requested by client
*/
private boolean isJitterEnabled;
/**
* @return
*/
public boolean isJitterEnabled() {
return isJitterEnabled;
}
/**
* Used jitterConfiguration with exponential backoff used
*
* @param maxAttempts maxAttempts
* @param maxDelayInMillis maxDelayInMillis after each retry,
* wait time increases exponentially until it reaches maxDelay
*
* @return ResiliencyHandler instance
*/
public ResiliencyHandler<R> usingJitterStrategy(int maxAttempts, long maxDelayInMillis) {
this.isJitterEnabled = true;
this.maxAttempts = maxAttempts;
this.maxDelayInMillis = maxDelayInMillis;
return new ResiliencyHandler<>(this);
}
/**
* With default jitter configuration used
*
* @return ResiliencyHandler instance
*/
public ResiliencyHandler<R> usingDefaultJitterStrategy() {
this.isJitterEnabled = true;
return new ResiliencyHandler<>(this);
}
/**
* Fixed delay configuration
*
* @param maxAttempts maxAttempts
* @param fixedDelayInMillis fixedDelayInMillis
*
* @return ResiliencyHandler instance
*/
public ResiliencyHandler<R> usingFixedDelayStrategy(int maxAttempts, long fixedDelayInMillis) {
this.maxAttempts = maxAttempts;
this.fixedDelayInMillis = fixedDelayInMillis;
return new ResiliencyHandler<>(this);
}
public void setAttemptsMade(int attemptsMade) {
this.attemptsMade = attemptsMade;
}
private void waitForNextAttempt() {
long delayDuration = isJitterEnabled() ? jitterDelay() : fixedDelay();
try {
log.info("Retry attempted after {} ms", delayDuration);
TimeUnit.MILLISECONDS.sleep(delayDuration);
} catch (InterruptedException interruptedException) {
log.debug("Failed to wait for next attempt", interruptedException);
}
}
private long fixedDelay() {
return fixedDelayInMillis;
}
/**
* Exponential back-off strategy
*
* @return delay in seconds
*/
public long exponentialDelay() {
long delay = (long) Math.pow(2, getAttemptsMade());
delay *= 1000;
if (delay <= 0) {
return maxDelayInMillis;
}
return Math.min(delay, maxDelayInMillis);
}
/**
* Exponential with jitter
*
* @return delay in seconds
*/
public long jitterDelay() {
double random = Math.random();
long jitterValue = Math.round(random * 1000);
return exponentialDelay() + jitterValue;
}
}
}
Comments
Post a Comment