Elevando a Resiliência: Implementando o Padrão Circuit Breaker com Cache Redis em Node.js
Quando se trata de criar sistemas robustos e confiáveis, a implementação do padrão Circuit Breaker é essencial. Este padrão ajuda a lidar com falhas em serviços externos, evitando chamadas repetidas quando o serviço está com problemas. Além disso, podemos melhorar o desempenho do nosso sistema ao adicionar um cache para armazenar respostas de serviços externos. Neste artigo, vamos explorar como implementar o padrão Circuit Breaker com cache Redis em Node.js.
Circuit Breaker
O padrão Circuit Breaker implementa cinco estados distintos para lidar com chamadas a serviços externos de forma mais resiliente. O estado "Close" é o estado inicial, onde as chamadas são permitidas a passar pelo Circuit Breaker normalmente. Quando um número significativo de chamadas começa a falhar, o Circuit Breaker entra no estado "Open", bloqueando todas as chamadas subsequentes e acionando um período de espera, evitando assim que a carga adicional seja direcionada ao serviço externo problemático. Depois de um tempo, ele entra no estado "Half-Open", permitindo que algumas chamadas sejam testadas novamente. Se essas chamadas forem bem-sucedidas, o Circuit Breaker retorna ao estado "Close", caso contrário, permanece em "Open". Os estados "Success" e "Fallback" representam eventos após uma chamada. "Success" ocorre quando uma chamada é bem-sucedida, enquanto "Fallback" ocorre quando uma chamada é substituída por um valor de fallback predefinido, ajudando a manter a funcionalidade quando o serviço externo está inacessível. Esses estados combinados oferecem uma estratégia poderosa para melhorar a resiliência de um sistema.
Entendendo o Código de Exemplo
Antes de prosseguir vamos dar uma olhada rápida no código fornecido.
O código usa o Node.js e a biblioteca Express para criar um servidor web. Ele utiliza o pacote axios para fazer chamadas a um serviço externo e o cache é configurado para armazenar respostas do serviço externo, com um tempo de vida (TTL) de um minuto.
O Circuit Breaker é implementado usando o pacote opossum. Este Circuit Breaker tem um tempo limite de 3 segundos, aciona o fallback se mais de 50% das chamadas falharem e espera 10 segundos antes de tentar reabrir o circuito.
import axios from "axios"
import express from "express"
import CircuitBreaker from "opossum"
import { createClient } from "redis"
const app = express()
const redisClient = createClient()
const redisTTL = 1 * 60 //one minute
const cacheKey = "api-response"
redisClient.on("error", (err) => console.log("Redis Client Error", err))
redisClient.connect()
const circuitBreakerOptions = {
timeout: 3000, // Timeout to call fallback Tempo limite para uma solicitação antes de acionar o fallback
errorThresholdPercentage: 50, // Porcentagem de erros antes de acionar o fallback
resetTimeout: 10000 // Tempo de espera antes de tentar reabrir o circuito
}
async function asyncHttpCall() {
const cacheResponse = await redisClient.get(cacheKey)
if (cacheResponse) return JSON.parse(cacheResponse)
const response = await axios.get("http://localhost:3001")
await redisClient.set(cacheKey, JSON.stringify(response.data), {
EX: redisTTL, //expire time, in seconds.
NX: true //only set the key if it does not already exist
})
return response.data
}
const circuitBreaker = new CircuitBreaker(asyncHttpCall, circuitBreakerOptions)
circuitBreaker
.on("close", () => console.log("CLOSE"))
.on("open", () => console.log("OPEN"))
.on("halfOpen", () => console.log("HALF"))
.on("success", () => console.log("SUCCESS"))
.on("fallback", () => console.log("FALLBACK"))
.fallback(() => {
return { id: 1, title: "iphone", price: "2.000" } //default object to return on fallback, here we can adopt any strategy
})
// External endpoint to call
app.get("/", async (req, res) => {
const response = await circuitBreaker.fire()
res.send(response)
})
app.listen(8080, () => {
console.log("Server running at door 8080!")
})
Cache
O objetivo de implementar o cache é armazenar as respostas do serviço no Redis e recuperá-las, evitando chamadas repetidas.
Primeiro verificamos se há uma resposta no cache Redis. Se a resposta estiver disponível, ela será retornada diretamente do cache, economizando chamadas ao serviço externo. Se a resposta não estiver no cache, fazemos a chamada ao serviço externo como antes, mas agora também armazenamos a resposta no Redis com um tempo de vida definido pelo “redisTTL”. Isso garante que as próximas chamadas possam aproveitar o cache.
Conclusão
A implementação do padrão Circuit Breaker com cache Redis pode melhorar significativamente a robustez e o desempenho de seus aplicativos. Evita chamadas repetidas a serviços externos quando estão com problemas e utiliza um cache para acelerar as respostas.
Lembre-se de que, em um ambiente de produção real, você precisará ajustar as configurações de acordo com suas necessidades e adicionar tratamento de erros mais abrangente. No entanto, esta implementação serve como um ponto de partida sólido para criar sistemas mais resilientes e eficientes em Node.js.
Repositório com o código completo github