Destino Dev

Blog

Implementando Circuit Breaker e Bulkhead em microservices NestJS com Cockatiel

Por John Pires · 09 de março de 2026

Implementando Circuit Breaker e Bulkhead em microservices NestJS com Cockatiel

Cenario pratico do dia a dia

O problema real: falhas em cadeia

Em arquiteturas de microservices, uma chamada lenta ou instável pode propagar falhas. Um downstream com latência crescente ou quedas intermitentes degrada a experiência do usuário e pode derrubar serviços dependentes. Sem mecanismos de contenção, o tráfego se acumula e a latência compõe em cascata.

Onde aplicar circuit breaker e bulkhead

Use circuit breaker para evitar que sistemas repetidamente tentem usar um recurso que está falhando. Use bulkhead para limitar concorrência e proteger recursos críticos de sobrecarga. Em NestJS, aplicamos ambos ao cliente HTTP usado para chamar serviços externos (HttpService/@nestjs/axios) ou adaptadores de cliente gRPC/AMQP.

Regra prática: proteja o ponto de falha mais crítico primeiro — normalmente a integração HTTP com terceiros ou com outro microservice.

Nos exemplos a seguir, consideramos um microservice que consome outro serviço via HTTP e precisa de resiliência. Assumimos familiaridade com módulos, providers e injeção do NestJS.

Arquitetura da solucao

Componentes principais

Arquitetura proposta:

  • ResilienceModule: cria e configura políticas de Cockatiel (circuit breaker, bulkhead, retry, timeout).
  • ResilientHttpService: wrapper injetável sobre HttpService que aplica políticas nas chamadas outbound.
  • Controladores e services do domínio: dependem de ResilientHttpService em vez de HttpService direto.
  • Fallbacks e adaptadores: tratam respostas quando políticas rejeitam/exaurem.

Fluxo de execução

Quando um controller solicita dados que dependem de uma chamada externa, o fluxo é:

  1. Controller chama Service de domínio.
  2. Service injeta ResilientHttpService e pede dados.
  3. ResilientHttpService executa a chamada via uma política combinada (bulkhead + circuit breaker + timeout [+ retry opcional]).
  4. Se a chamada for bem-sucedida, resposta retorna normalmente; se houver falha, a política decide: retry (se aplicável), abrir circuito ou rejeitar por bulkhead.
  5. Em casos de circuito aberto ou bulkhead saturado, retornamos uma resposta controlada ou lança exceção tratada por filtros globais.

Esse design separa responsabilidade de resiliência do código de negócio e mantém testabilidade.

Codigo completo com explicacoes

1) Instalacao e dependencias

Instale bibliotecas necessárias:

npm install @nestjs/axios axios cockatiel

Explicacao: @nestjs/axios fornece HttpService (baseado em Axios). Cockatiel é a lib de resiliência que fornece circuit breaker, bulkhead e outras políticas.

2) ResilienceModule: configurando as politicas

Este módulo cria instâncias de políticas e as registra como providers para reuso. Ajuste parâmetros conforme SLAs e comportamento real do seu ambiente.

import { Module, Global } from '@nestjs/common';
import { Policy } from 'cockatiel';

const circuitBreakerPolicy = Policy.handleAll().circuitBreaker(30000, 5);
const bulkheadPolicy = Policy.bulkhead(10, 50);

@Global()
@Module({
  providers: [
    {
      provide: 'RESILIENCE_POLICIES',
      useValue: {
        circuitBreakerPolicy,
        bulkheadPolicy,
      },
    },
  ],
  exports: ['RESILIENCE_POLICIES'],
})
export class ResilienceModule {}

Contexto: o código cria políticas e as exporta globalmente. Policy.handleAll() captura exceções para a política de circuit breaker. Ajuste os parâmetros: o primeiro valor em circuitBreaker é tempo de meia-abertura e o segundo, limiar de falhas consecutivas antes de abrir (valores ilustrativos).

3) ResilientHttpService: wrapper que aplica politicas

O wrapper centraliza a aplicação das políticas em métodos HTTP comuns (get/post/put/delete). Aqui usamos toPromise() para converter o Observable do HttpService para Promise.

import { Injectable, Inject } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class ResilientHttpService {
  constructor(
    private readonly http: HttpService,
    @Inject('RESILIENCE_POLICIES') private readonly policies: any,
  ) {}

  private wrapCall<T>(fn: () => Promise<T>): Promise<T> {
    const { circuitBreakerPolicy, bulkheadPolicy } = this.policies;
    const combined = Policy.wrap(bulkheadPolicy, circuitBreakerPolicy);
    return combined.execute(() => fn());
  }

  async get<T>(url: string, config?: any): Promise<T> {
    return this.wrapCall(() => firstValueFrom(this.http.get<T>(url, config)));
  }

  async post<T>(url: string, body: any, config?: any): Promise<T> {
    return this.wrapCall(() => firstValueFrom(this.http.post<T>(url, body, config)));
  }
}

O que acontece: antes de executar uma chamada HTTP, combinamos as politicas (bulkhead + circuit breaker). Se o bulkhead estiver cheio ou o circuito aberto, a Promise será rejeitada conforme implementado pela cockatiel.

4) Usando o ResilientHttpService no dominio

Exemplo de service que consome outro microservice usando o wrapper.

import { Injectable, HttpException } from '@nestjs/common';
import { ResilientHttpService } from './resilient-http.service';

@Injectable()
export class OrdersService {
  constructor(private readonly http: ResilientHttpService) {}

  async fetchOrderDetails(orderId: string) {
    try {
      const res = await this.http.get(`http://inventory.svc.local/items/${orderId}`);
      return res.data ?? res; // depende do formato do retorno
    } catch (err) {
      // transformamos em excecao do Nest para filtros padrao
      throw new HttpException('Serviço de inventory indisponível', 503);
    }
  }
}

Depois: o controller injeta OrdersService normalmente. Note o tratamento de excecao para mapear erros de resiliência para códigos HTTP adequados.

Tratamento de erros e edge cases

Erros comuns e armadilhas

Listagem dos pontos de atenção:

  • Usar params de circuit breaker muito sensíveis (ex: limiar baixo) pode gerar circuitos abertos indevidos.
  • Não combinar timeout com circuit breaker: se timeout efetivamente cancela a request, a política deve contar esse evento como falha.
  • Bulkhead sem fila adequada pode retornar erro sob pico subitamente; decida entre fila curta ou rejeitar imediatamente.
  • Conversão de Observable para Promise: firstValueFrom pode lançar se stream completa sem valor — adapte conforme a forma que HttpService retorna o corpo.

Tratamento de respostas quando politicas rejeitam

Ao detectar um circuito aberto ou bulkhead saturado, não reenvie indiscriminadamente. Decisões possíveis:

  • Retornar resposta degradada (cache local, resposta estática).
  • Retornar fallback mínimo informando indisponibilidade com 503/429.
  • Implementar uma fila assíncrona (background retry) se a operação puder ser eventual.

Exemplo de catch detalhado no wrapper para mapear saturação de bulkhead para 429 (Too Many Requests):

import { HttpException } from '@nestjs/common';

// dentro do ResilientHttpService.wrapCall
return combined.execute(() => fn()).catch(err => {
  // err.code ou err.message depende da implementacao da cockatiel
  if (err.message && err.message.includes('Bulkhead')) {
    throw new HttpException('Serviço sobrecarregado, tente novamente mais tarde', 429);
  }
  if (err.message && err.message.includes('Circuit is open')) {
    throw new HttpException('Dependência com problema, operação bloqueada', 503);
  }
  throw err; // rethrow para handlers superiores
});

Contexto: adapte as mensagens de erro de acordo com a versão da cockatiel — a ideia é identificar a causa e traduzir para um código HTTP sem vazar detalhes internos.

Performance e otimizacao

Métricas e observabilidade

Medir é crítico. Colete métricas por política:

  • Contagem de aberturas de circuito
  • Taxa de rejeição do bulkhead
  • Latência das chamadas protegidas

Exporte essas métricas para Prometheus ou outro observability stack. Por exemplo, incremente contadores quando a política dispara fallback. Assim você ajusta parâmetros com dados reais.

Otimizações práticas

Boas práticas que impactam performance e estabilidade:

  • Dimensione bulkhead com base em capacidade real: threads/processo e latência média da operação.
  • Use timeout na camada HTTP (Axios) para liberar conexões e evitar esperas infinitas.
  • Combine circuit breaker com retry com backoff exponencial, mas limite o retry para não congestionar o sistema.
  • Seed time windows com produção de dados reais; não assuma taxas de erro em dev.

Exemplo de configuração que adiciona timeout e retry (ilustrativo):

const timeoutPolicy = Policy.timeout(5000); // aborta se >5s
const retryPolicy = Policy.handleAll().retry().attempts(2).exponential(100, 1000);
const combined = Policy.wrap(bulkheadPolicy, circuitBreakerPolicy, retryPolicy, timeoutPolicy);

O que acontece: as políticas são aplicadas na ordem do wrap — bulkhead controla concorrência; circuit breaker evita chamadas a recursos com falha; retry tenta reexecutar quando aplicável; timeout garante que chamadas longas sejam abortadas.

Consideracoes finais e conclusao pratica

Checklist para implantar em produção

Antes de rodar em produção verifique:

  1. Parâmetros das políticas baseados em testes de carga e em latências reais.
  2. Métricas e alertas configurados para alterações de estado do circuit breaker.
  3. Fallbacks definidos (cache, mensagens ou respostas degradadas) que preservem a experiência mínima.
  4. Tratamento adequado das exceções no código de negócio (HTTP status map, logs, correlation ids).

Resumo prático

Separar a lógica de resiliência em um módulo e wrapper injetável simplifica a adoção das políticas por toda a base de código. Use Bulkhead para limitar concorrência, Circuit Breaker para evitar tentativas inúteis a dependências quebradas e combine com timeout e retry quando fizer sentido. Observe métricas e ajuste parâmetros com dados reais.

Implementar circuit breaker e bulkhead com Cockatiel em NestJS exige atenção a detalhes: escolha correta de limites, tratamento de erros e integração com observabilidade. Ao seguir um padrão centralizado (ResilienceModule + ResilientHttpService) você ganha consistência e mantém o código de domínio limpo.

Próximos passos práticos: instrumente um endpoint crítico com as políticas, gere tráfego sintético para observar comportamento em pico e refine limiares até equilibrar disponibilidade e proteção.

Assuntos: NestJS, microservices, cockatiel, circuit-breaker, bulkhead, resilience

Leia também