Services in contemporary microservices architectures frequently depend on one another for proper operation. Nevertheless, if one of these interdependent services malfunctions or is sluggish, it may have a knock-on impact that lowers system performance as a whole. Through the prevention of cascade failures and the management of request flow to a failing service, the Circuit Breaker pattern is a design pattern that enhances the stability and resilience of distributed systems. We'll examine the Circuit Breaker paradigm, its advantages, and real-world applications using TypeScript and Node.js in this article.
What is the Circuit Breaker Pattern?
A fault-tolerance technique called the Circuit Breaker pattern encircles a function or service call and keeps an eye on its operation. If it notices that the service is malfunctioning or going unresponsive, it "breaks" the circuit. By doing this, new requests cannot be delivered to the failing service, allowing it to recover without being overloaded.
Key States of a Circuit Breaker:
✓ Closed: The circuit is functioning normally, and requests are passed through to the service.
✓ Open: The circuit is broken, requests are blocked, and an error is returned immediately without attempting to call the service.
✓ Half-Open: The circuit attempts to reset by allowing a limited number of requests to pass through. If these requests are successful, the circuit moves to the closed state; otherwise, it goes back to the open state.
Benefits of the Circuit Breaker Pattern
🌱 Improves System Resilience: By preventing requests from reaching an already failing service, it protects the overall system from further degradation.
🌱 Reduces Latency: Instead of waiting for timeouts from failing services, the Circuit Breaker quickly returns an error, reducing the response time.
🌱 Enables Graceful Recovery: Allows a service to recover without the pressure of continuous incoming requests.
Implementing a Circuit Breaker in Node.js with TypeScript
Let’s create a simple Circuit Breaker class in TypeScript and use it to wrap a service call in a Node.js application.
Step 1: Setting Up the Project
First, create a new Node.js project with TypeScript:
mkdir circuit-breaker-example
cd circuit-breaker-example
npm init -y
npm install typescript ts-node @types/node axios
npx tsc --init
Step 2: Creating the Circuit Breaker Class
Here is a basic implementation of a Circuit Breaker in TypeScript:
// circuitBreaker.ts
type State = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
class CircuitBreaker {
private failureThreshold: number;
private recoveryTimeout: number;
private successThreshold: number;
private failures: number;
private state: State;
private lastFailureTime: number | null;
private successes: number;
constructor(failureThreshold: number, recoveryTimeout: number, successThreshold: number) {
this.failureThreshold = failureThreshold;
this.recoveryTimeout = recoveryTimeout;
this.successThreshold = successThreshold;
this.failures = 0;
this.state = 'CLOSED';
this.lastFailureTime = null;
this.successes = 0;
}
private moveToState(newState: State) {
this.state = newState;
if (newState === 'CLOSED') {
this.failures = 0;
this.successes = 0;
}
}
async call<T>(action: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (this.lastFailureTime && (Date.now() - this.lastFailureTime) > this.recoveryTimeout) {
this.moveToState('HALF_OPEN');
} else {
throw new Error('Circuit is open');
}
}
try {
const result = await action();
if (this.state === 'HALF_OPEN') {
this.successes++;
if (this.successes >= this.successThreshold) {
this.moveToState('CLOSED');
}
}
return result;
} catch (error) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.moveToState('OPEN');
this.lastFailureTime = Date.now();
}
throw error;
}
}
}
export default CircuitBreaker;
This Circuit Breaker class has three key states: CLOSED, OPEN, and HALF_OPEN. It tracks the number of failures, and if they exceed the failure threshold, it opens the circuit. After a recovery timeout, it moves to the half-open state, where it tests the service with a limited number of requests.
Step 3: Using the Circuit Breaker in a Node.js Application
Let’s use the Circuit Breaker class to wrap an HTTP request made with Axios:
// index.ts
import axios from 'axios';
import CircuitBreaker from './circuitBreaker';
// Define an action that will be wrapped by the Circuit Breaker
const fetchData = async () => {
const response = await axios.get('https://api.example.com/data');
return response.data;
};
// Create a Circuit Breaker instance
const circuitBreaker = new CircuitBreaker(3, 5000, 2);
const run = async () => {
for (let i = 0; i < 10; i++) {
try {
const data = await circuitBreaker.call(fetchData);
console.log('Data fetched:', data);
} catch (error) {
console.error('Error:', error.message);
}
// Simulate a delay between requests
await new Promise(resolve => setTimeout(resolve, 1000));
}
};
run();
In this example, the fetchData function is wrapped by the Circuit Breaker. If the service fails three times (as per the failureThreshold), the circuit moves to the open state and stops calling the service. After 5 seconds (recoveryTimeout), it tries again in the half-open state. If it succeeds twice (successThreshold), it closes the circuit.
A crucial technique for creating robust microservices and distributed systems is the Circuit Breaker pattern. A circuit breaker can lower latency, stop cascading failures, and enable services to restart smoothly. This post explained how to use TypeScript to create a simple Circuit Breaker in Node.js. This serves as a basis for adding more sophisticated features like logging, monitoring, and fallback procedures. To improve the stability and dependability of your applications, start adding circuit breakers right now!
That's all folks 👋🏻
Top comments (1)
I read your post with interest, thank you for your post.