Hands-on: aplicando o Circuit Breaker na prática

Itaú Tech
ItauTech
Published in
6 min readNov 21, 2023

por Michael Douglas Barbosa Araujo, Subject Matter Expert na Comunidade de Soluções de Investimentos no Itaú

A imagem mostra a frase “Hands-on: Circuit Breaker. Confira um exemplo prático dessa potente ferramenta em ação” no lado esquerdo, sobre um fundo laranja. No lado direito, há a foto de uma mão branca, digitando em um teclado de notebook.

No último artigo, abordamos toda a teoria por trás da ferramenta chamada “Circuit Breaker”, uma potente solução para a área de microsserviços utilizada no aplicativo de investimentos do Itaú — o íon. Essa solução, capaz de nos ajuda a lidar com possíveis falhas de serviços dependentes, se trata de um padrão de projeto utilizado em arquitetura de softwares que podemos encontrar em diversas aplicações, o que inclui também algumas soluções internas que utilizamos no banco.

Já conferiu a primeira parte do artigo e absorveu toda a teoria sobre o tópico? Então agora vamos entender como ele se aplica na prática.

Vamos ao código!

E como diria Linus Torvalds, “vamos ao código”! Ou seja, agora é o momento de vermos na prática o que falamos até o momento.

Essa imagem é uma famosa frase de Linus Torvalds que comenta: falar é fácil. Mostre-me o código.
Falar é fácil — mostre-me o código.

Um adendo antes de colocarmos a mão na massa: em nosso exemplo criaremos um Circuito Fechado utilizando Python com Pybreaker. No entanto, saiba que que existem outras bibliotecas muito boas e que o nosso exemplo poderia ser facilmente criado em qualquer uma delas. Seguem alguns exemplos:

circuit-breaker (Python)
pybreaker (Python)
Hystrix (Java)
Polly (.NET)
Opossum (Node.js)

Dito isso, bora praticar? Para iniciar, crie uma pasta em seu ambiente com o nome de sua preferência. Depois, abra o terminal nesta pasta e então execute o comando a seguir para criar a venv:

python3 -m venv venv

Ah, lembrando que os comandos aqui exemplificados foram executados em uma máquina Windows. Caso tenha um sistema operacional diferente, verifique como fazer o mesmo com o seu sistema. Agora vamos ativar o ambiente executando o comando:

source venv/Scripts/activate

Abra uma IDE ou editor de códigos de sua preferência. Para o nosso tutorial irei utilizar o Visual Studio Code. Sendo assim, irei executar na raiz do projeto o comando:

source venv/Scripts/activate

Após abrir o VSCode, volte ao terminal para instalarmos o Flask e requests. Para isso, execute no terminal:

pip install Flask
pip install requests

Agora, crie uma pasta chamada api dentro da pasta que acabou de criar, e então crie um script chamado app.py — esse script será responsável por criar uma pequena api que estará encarregada em devolver casos de sucesso, falha e randomizar entre os status. Lembre-se de deixar a estrutura da seguinte maneira:

import random 
import time

from flask import Flask
app = Flask(__name__)

@app.route('/success')
def success_endpoint():
return {
"message": "Chamada realizada com sucesso."
}, 200

@app.route('/failure')
def faulty_endpoint():
r = random.randint(0, 1)
if r == 0:
time.sleep(2)

return {
"message": "Falhou."
}, 500

@app.route('/random')
def fail_randomly_endpoint():
r = random.randint(0, 1)
if r == 0:
return {
"message": "Chamada realizada com sucesso."
}, 200

return {
"message": "Falhou (algumas vezes)."
}, 500

Após a criação, execute o comando a seguir na pasta api para subir o Flask:

flask run 

E caso tudo esteja tudo correto, você terá como retorno:

Running on http://127.0.0.1:5000 

Agora, criaremos dois tipos de teste, no qual no primeiro faremos um loop para verificar o estado do circuito ao chamar a nossa api. Você irá verificar que em nosso código existe uma condição para, em alguns momentos, chamar o endpoint de falha, isso porque ele tem uma regra de timeout e o Random. Esse teste é bem bacana, porque nos possibilitar passar por quase todos os estados possíveis.

Sendo assim, crie na raiz do projeto o script testLoop.py e deixe-o com a seguinte estrutura:

import requests 
from pybreaker import CircuitBreaker, CircuitBreakerError
from colorama import Fore, Style

# Configurações do circuit breaker
failure_threshold = 3 # Número máximo de falhas consecutivas permitidas
success_threshold = 5 # Número mínimo de sucessos consecutivos necessários
# Tempo de espera em segundos antes de tentar novamente após o circuit breaker bloquear a chamada
reset_timeout = 3

# Cria o circuit breaker
breaker = CircuitBreaker(fail_max=failure_threshold,
reset_timeout=reset_timeout)

@breaker
def call_endpoint(value):
# Se for par, chame o endpoint /failure
# Se for ímpar, chame o endpoint /random
endpoint = '/failure' if value % 2 == 0 else '/random'

response = requests.get(f'http://localhost:5000{endpoint}')
# Lança uma exceção caso a resposta não seja bem-sucedida
response.raise_for_status()
return response.json()

# Executa o loop para testar o circuit breaker
for i in range(1, 11):
print(f"Executando iteração {i}")

try:
result = call_endpoint(i)
print(Fore.GREEN + 'Chamada bem-sucedida!')
print(Fore.GREEN + f"Resultado: {result}")
print(Style.RESET_ALL)
except CircuitBreakerError:
print(Fore.RED + 'Chamada bloqueada pelo circuit breaker')
print(Style.RESET_ALL)
except requests.exceptions.RequestException as e:
print(Fore.RED + f'Erro ao chamar o endpoint: {e}')
print(Style.RESET_ALL)

print("---------------------------")

print("Loop concluído!")

E para fazer o nosso teste no terminal, execute:

python3 testLoop.py 

Cada execução terá um retorno. Em meu caso, obtive como resposta:

Executando iteração 1 
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 2
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 3
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 4
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 5
Chamada bem-sucedida!
Resultado: {'message': 'Chamada realizada com sucesso.'}
---------------------------
Executando iteração 6
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/failure
---------------------------
Executando iteração 7
Erro ao chamar o endpoint: 500 Server Error: INTERNAL SERVER ERROR for url: http://localhost:5000/random
---------------------------
Executando iteração 8
Chamada bloqueada pelo circuit breaker
---------------------------
Executando iteração 9
Chamada bloqueada pelo circuit breaker
---------------------------
Executando iteração 10
Chamada bloqueada pelo circuit breaker
---------------------------
Loop concluído!

Para fecharmos o nosso teste, vamos criar um script Flask, que será um serviço consumidor da nossa api onde a função dele é consumir a api. Mas não se esqueça de verificar se ela está funcionando normalmente com o padrão Circuit Breaker.

Para isso criaremos um script novo na raiz, chamado testCall.py, e deixaremos com a seguinte estrutura de código:

from flask import Flask, jsonify 
from pybreaker import CircuitBreaker, CircuitBreakerError
import requests

app = Flask(__name__)

# Configurações do circuit breaker
failure_threshold = 3 # Número máximo de falhas consecutivas permitidas
success_threshold = 5 # Número mínimo de sucessos consecutivos necessários
# Tempo de espera em segundos antes de tentar novamente após o circuit breaker bloquear a chamada
reset_timeout = 10

# Cria o circuit breaker
breaker = CircuitBreaker(fail_max=failure_threshold,
reset_timeout=reset_timeout)

@breaker
def check_service_status():
response = requests.get('http://127.0.0.1:5000/random')
if response.status_code == 200:
return True
else:
raise Exception('O serviço está fora do ar!')

@app.route('/status')
def status():
try:
check_service_status()
return jsonify({'status': 'Serviço está funcionando corretamente!'})
except CircuitBreakerError:
return jsonify({'status': 'Serviço está com problemas!'})
except Exception as e:
return jsonify({'status': str(e)})

@app.route('/service')
def service():
return jsonify({'data': 'Dados do serviço'})

if __name__ == '__main__':
app.run(port=8080)

E agora, para realizar o teste, vamos subir o nosso serviço. Para isso execute o comando na raiz do projeto:

flask --app testeCall run --port=8080 
Caso tudo esteja correto você terá como retorno:
* Serving Flask app “testeCall”
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:8080
Press CTRL+C to quit

Por fim, podemos realizar o teste ao chamar a url: 127.0.0.1:8080/status. Repare que em alguns momentos ele informa que está tudo correto, e em outros que não. Isso porque estamos chamando o endpoint randômico que faz com que se tenha esse estado.

Como demonstrado, o Circuit Breaker pode ser uma ferramenta poderosa para garantir a resiliência em ambientes baseados em microsserviços. Monitorando a comunicação entre os serviços e interrompendo a comunicação em um serviço que apresenta falhas, neste artigo, você pôde ter um passo-a-passo de sua aplicação e validação.

Já utilizou o Circuit Breaker ou aplica a ferramenta no seu dia a dia? Compartilhe com a gente as suas percepções nos comentários! Tem alguma dúvida sobre o conteúdo ou gostaria de ler sobre algum outro tema por aqui? Pode dividir com a gente também para continuarmos a nossa conversa. ;)

--

--