Adding Circuit Breakers to Node.Js APIs
What are circuit breakers?
Software design pattern used to determine the availability of an upstream service(API,database,etc).
When the error or timeout count for the service exceeds a predetermined threshold, a circuit breaker should “trip” to halt an application’s requests to a dependent service for a period of time.
The circuit breaker’s core concept is fairly simple. A circuit breaker object, which checks for failures, is used to surround a protected function call. When the number of failures reaches a specific level, the circuit breaker trips, and any further calls to the circuit breaker fail with an error, without the protected call being made at all.
Why you need circuit breakers?
Circuit breakers are particularly critical when many services rely on one another. If one service fails, the entire architecture may be brought down.
How do they work?
A circuit breaker works as a proxy when micro-service route failed. This proxy will keep track of the number of failures and use that information to determine whether to proceed or immediately throw an exception.
The proxy can be built with the following states, such as electric circuit breaker functionality.
CLOSED — Resource has not been tried yet or has been tried and is available
OPEN — Resource was tried and was unavailable breaker trips
HALF-OPEN — Wait threshold met resources was tried again
When to use this pattern
- When an application route attempts to call a third-party route
- When a route fails due to a problem with a third-party route, have a fallback to send a static response for a specified interval.
- Creating a fault-tolerant application in which the failure of one or more services does not bring the entire programme down.
When not to use this pattern
- Do not use as a replacement for managing exceptions in application route
- Do not use for accessing local private resources in an application.
Implementation on Express JS backend
Let’s take a simple node js microservice and implement a circuit breaker pattern to the routes
We have the best three npm modules for dealing with circuit breakers.
My personal preference among the three is Mollitia, which is simple and easy to use, and from which we can develop modules and create reusable logic.
Let’s have a look at how we can incorporate a circuit breaker using the Mollitia module in the example below.
This application is divided into two sections. A third-party service has been introduced to the circuit breaker to fetch order details from order-service
First, let’s configure the circuit breaker as shown below to determine when it should open and close.
const MollitiaPrometheus = require('@mollitia/prometheus')
const Mollitia = require('mollitia')
const { Circuit, Fallback, SlidingCountBreaker, BreakerState } = Mollitiaconst config = {
name: 'appCounter',
slidingWindowSize: 6, // Failure Rate Calculation
minimumNumberOfCalls: 3, // 3 iterations are needed to start
failureRateThreshold: 60,
slowCallDurationThreshold: 500,
slowCallRateThreshold: 50,
permittedNumberOfCallsInHalfOpenState: 2,
openStateDelay: 10000,
halfOpenStateMaxDelay: 30000,
}// Sliding counter
const slidingCountBreaker = new SlidingCountBreaker(config)
// Create fallback
const fallback = new Fallback({
callback(err) {
// Every time the method rejects, You can filter here
if (err) {
return err.message
}
},
})
// Creates a circuit
const orderCircuit = new Circuit({
name: 'Order Operations',
options: {
prometheus: {
name: 'orderCircuit',
},
modules: [slidingCountBreaker, fallback],
},
})
To know for more configuration options check the details here
Let us replace the route controller call with a circuit, which will accept function parameters and allow us to call a third party or remote service. In below code snippet orderController.getOrders(req.query.category) is the function which handles the operation for remote calls.
To obtain the current state of the circuit, utilise the slidingCountBreaker variable, verify the state, and handle the necessary action that must be returned.
app.get('/orders', (req, res) => {
orderCircuit
.fn(() => orderController.getOrders(req.query.category))
.execute()
.then((result) => {
console.log('Circuit State -->', slidingCountBreaker.state)
res.send(result)
})
.catch((error) => {
console.log('Circuit State -->', slidingCountBreaker.state)
if (slidingCountBreaker.state === BreakerState.CLOSED) {
res.send({
status: false,
message: "Order service is down",
})
} else {
// Fallback Order response
}
})
})
The code below calls the order service to retrieve the data; as you can see, we utilised the httpRequest module to do so. If the response is satisfactory, we will return; otherwise, we will reject. Once the reject is called, the flow enters the circuit breaker catch block, where the fallback is called based on the configuration and breaker condition.
const orderController = {
getOrders: (category) => {
return new Promise((resolve, reject) => {
httpRequest(
{
uri: `http://localhost:9191/orders?category=${category}`,
method: 'GET',
},
(error, response, body) => {
if (error) {
reject(error)
} else if (response) {
if (response.statusCode === 200) {
resolve(JSON.parse(body))
} else {
resolve(response.body)
}
}
}
)
})
},
}
Run the server and orders service, you can also clone the code repo from here. npm run start and npm run orders
Now, call the endpoint http://localhost:3000/orders?category=electronics that was enabled with the circuit breaker; if the flow is successful, the application returns the response obtained from the order service.
Now stop the order service.So, when we stop the idel case, the service should be offline.
The response will be returned from fallback after 3 times, as set on the circuit config
You can also see that the circuit is open and half-opened. This show the flow goes via the circuit breaker
Let’s restart the order service by running npm run orders again. Now, curl to http://localhost:3000/orders?category=electronics. The circuit has been closed, and normal flow has resumed.
We may also generate metric dashboards from the above states. To do so, add the plugin mollitia-prometheus to the circuit breaker.
const MollitiaPrometheus = require('@mollitia/prometheus')
const Mollitia = require('mollitia')
const app = express()
const { Circuit, Fallback, SlidingCountBreaker, BreakerState } = Mollitia
app.use(express.json({ urlencoded: true }))
// PrometheusAddon
Mollitia.use(new MollitiaPrometheus.PrometheusAddon())
To fetch the stats create and endpoint as below and connect to monitoring tools
app.get('/stats', (req, res) => res.send(MollitiaPrometheus.metrics()))
You will get metrics response like below
Code Repository: