DEV Community

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

Posted on • Edited on

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

Top comments (0)