In this article, we will explore the concepts of the Retry and Circuit Breaker patterns, and gain insight into when and why we should implement them.
At times, it is necessary to utilize additional services, including our services or third-party services such as payment services. Imagine that in our code, we call an API of a payment service and at that moment, the payment service is under load and cannot respond to our request. Alternatively, there may be network latency or issues with any infrastructural services, causing our request to not be processed. However, if we send the request again, the destination service may be off-loaded or the infrastructural problem may be resolved, and our request may be successfully responded to.
What should we do in this situation?
one of our responsibilities as a developer is to increase tolerance of the failure of the system, The developed service should be compatible and bulletproof in different scenarios and situations and deliver a seamless user experience.
we know that if we try multiple times, one or two more times may receive a successful response and we should not respond to our users an error because the third-party service throws an error on the first try. However, it is important to note that some errors are not retriable like UnAuth, access denied, etc.
Now let's dive into another concept, Jitter!
imagine a situation in which many clients try to call a specific service, that service will be on-loaded and fail to respond to some of the clients, and any client who receives a failed response tries to call the service at the same time, and the service will be on-load again and make the service denial of service, it is better to make a delay between retries by this formula:
a constant Ms + random Ms
it helps us to call the service at random times and help the service to heal itself
below is a retry utils service implementation with jitter that is compatible with Axios:
import { Injectable } from '@nestjs/common';
import { AxiosResponse } from 'axios';
export type AxiosMethod = () => Promise<AxiosResponse>;
@Injectable()
export class Retry {
constructor() {}
async retry(
axiosMethod: AxiosMethod,
retry: number,
delayInMs: number,
jitter: boolean,
): Promise<AxiosResponse> {
try {
let res: AxiosResponse | null = null;
for (let i = 0; i <= retry; i++) {
try {
res = await axiosMethod();
break;
} catch (err) {
if (i < retry) {
const j = this.getJitter(jitter);
await this.executeWithDelay(delayInMs + j);
continue
} else {
throw err;
}
}
}
return res;
} catch (error) {
throw error;
}
}
private executeWithDelay(delay: number) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
private getJitter(jitter: boolean) {
return jitter ? Math.floor(Math.random() * (200 - 50 + 1)) + 50 : 0;
}
}
I hope this description can assist you in developing your projects with a high tolerance for failure. Feel free to ask any questions.👌
Related resources
https://learn.microsoft.com/en-us/azure/architecture/patterns/retry