DEV Community

Cover image for Padrão - Circuit Breaker
Humberto Oliveira Barbosa
Humberto Oliveira Barbosa

Posted on • Edited on

2

Padrão - Circuit Breaker

Como deixar seu sistema mais resiliente e preparado para falhas?

Resiliência nada mais é do que a capacidade do seu sistema de se recuperar de falhas e continuar funcionando normalmente. Parece óbvio, né? Mas na prática, garantir isso pode ser um desafio. Quem popularizou esse conceito foi Michael Nygard.

Por que o nome Circuit Breaker? 💡

O nome vem de um conceito bem simples: ele funciona exatamente como um disjuntor elétrico. Quando há uma sobrecarga de energia na sua casa, o disjuntor desarma e corta o fluxo para evitar danos. Depois, alguém precisa ir lá e reativá-lo manualmente.

No software, a lógica é parecida. Imagine que seu sistema está recebendo um fluxo alto de requisições e precisa se comunicar com uma API externa. Se essa API começa a demorar demais para responder (timeout de 30 segundos, por exemplo), você pode acabar sobrecarregando seu servidor, causando um efeito dominó que pode levar ao colapso da aplicação.

É aqui que entra o Circuit Breaker. Ele adiciona uma camada de proteção para evitar falhas em cascata. Se uma requisição remota demora muito ou falha, o sistema identifica isso e retorna uma resposta alternativa, evitando que a aplicação fique travada tentando falar com um serviço que não responde.


Estados do Circuit Breaker

O Circuit Breaker pode estar em três estados:

  • Closed (Fechado): O fluxo de requisições segue normalmente para a API externa. Tudo certo aqui.
  • Open (Aberto): Se as falhas acumularem, o disjuntor abre, impedindo que novas requisições cheguem à API remota. Isso evita que o sistema fique sobrecarregado.
  • Half-Open (Semiaberto): Após um tempo, o Circuit Breaker permite algumas requisições para testar se o serviço externo voltou ao normal. Se der certo, ele fecha de novo. Se continuar falhando, volta para o estado aberto.

Você pode estar se perguntando: mas por que existe o estado Half-Open?

No seu disjuntor de casa, depois que ele desarma, alguém precisa ir lá religá-lo manualmente. Mas no software, não faz sentido depender de um ser humano para isso. Então, quando o Circuit Breaker abre (erro detectado), ele espera um tempo antes de tentar se reconectar automaticamente.

Se a API remota voltar a funcionar dentro do tempo configurado (ex: 200ms, 1s etc.), o circuito fecha e tudo volta ao normal. Mas se ainda estiver falhando, ele continua aberto e para de tentar falar com o serviço problemático.


Implementação prática em Node.js

Aqui está um exemplo simples que você pode rodar para ver o Circuit Breaker funcionando na prática.

O que o código faz:

  • Um servidor rodando na porta 3000, que faz chamadas para outro servidor na porta 3001 (simulando uma API externa).
  • O serviço externo às vezes responde normalmente (200 OK) e às vezes demora ou retorna erro (400), simulando falhas.
  • Usamos a biblioteca opossum para gerenciar o Circuit Breaker.
  • Se houver falhas acumuladas, o Circuit Breaker abre e para de enviar requisições para a API.

Código:

import express from "express";
import fetch from 'node-fetch';
import CircuitBreaker from 'opossum';

const app = express();
const app2 = express();

// Simula uma requisição para um serviço externo
async function asyncFunctionThatCouldFail() {
  return new Promise((resolve, reject) => {
    fetch('http://localhost:3001/')
        .then(res => res.json())
        .then(json => resolve(json))
        .catch(err => reject(err));
  });
}

// Configuração do Circuit Breaker
const options = {
  timeout: 100, // Se a resposta demorar mais que 100ms, falha
  errorThresholdPercentage: 50, // Quando 50% das requisições falham, o circuito abre
  resetTimeout: 10000 // Após 10s, ele tenta novamente
};

const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);

// Rota principal que dispara requisições
app.get("/", async (req, response) => {
  breaker.fire()
    .then(res => response.json(res))
    .catch(err => response.status(500).json(err));
});

// Simulando um serviço externo que falha às vezes
app2.get("/", (req, res) => {
    const randomNumber = Math.round(Math.random() * 10);
    console.log(randomNumber);

    if (randomNumber <= 5) {
      res.status(200).json({ msg: "Success!" });
    } else {
      setTimeout(() => {
        res.status(400).json({ msg: "Failed!" });
      }, 150);
    }
});

app.listen(3000, () => console.log(`Listening at http://localhost:3000`));
app2.listen(3001, () => console.log(`Listening at http://localhost:3001`));

// Eventos do Circuit Breaker
breaker.on('open', () => console.log('Circuito ABERTO - Parando requisições! 🚨'));
breaker.on('halfOpen', () => console.log('Circuito SEMIABERTO - Testando novamente... 🔄'));
breaker.on('close', () => console.log('Circuito FECHADO - Tudo normal! ✅'));
Enter fullscreen mode Exit fullscreen mode

Código no Git

Você pode conferir esse código no repositório:

🔗 https://github.com/h1bertobarbosa/circuit-breaker-pattern


Referências utilizadas

🔹 AppSignal - Circuit Breaker em Node.js

🔹 Microsoft - Padrão Circuit Breaker

🔹 Martin Fowler - Explicação detalhada

🔹 Medium - Circuit Breaker para microservices

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay