1.0.3 • Published 4 months ago

resilience-kit v1.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

Resilience Kit proporciona patrones de resiliencia como Circuit Breaker, Retry, Timeout y Fallback para NestJS (y Node.js). Permite configurar y aplicar estos patrones de forma sencilla mediante interceptores, decoradores y un módulo dinámico.

🚀 Características principales

Circuit Breaker: Protege tu aplicación de fallos repetitivos o servicios inestables.
Retry: Reintenta automáticamente una operación fallida.
Timeout: Detiene las operaciones que toman demasiado tiempo.
Fallback: Devuelve una respuesta alternativa cuando una operación falla.

📦 Instalación

Puedes realizar la instalación con NPM:

npm install resilience-kit

O usando Yarn:

yarn add resilience-kit

Requisitos

  • NestJS (v9 o superior recomendado).
  • Node.js 16+ (para soporte de ES2020).

📌 Uso básico en NestJS

1️⃣ Importar el módulo

En tu AppModule (o el módulo donde lo necesites), importa ResilienceModule y configura los patrones deseados:

import { Module } from "@nestjs/common";
import { ResilienceModule } from "resilience-kit";

@Module({
  imports: [
    ResilienceModule.forRoot({
      circuitBreaker: {
        enabled: true,
        timeout: 2000,
        errorThresholdPercentage: 50,
        resetTimeout: 10000,
      },
      retry: {
        enabled: true,
        maxRetries: 3,
        delayMs: 500,
      },
      timeout: {
        enabled: true,
        timeoutMs: 3000,
      },
      fallback: {
        enabled: true,
        fallbackMethod: () => ({ message: "Fallback result" }),
      },
    }),
  ],
})
export class AppModule {}

Si necesitas cargar la configuración de forma asíncrona, usa forRootAsync():

ResilienceModule.forRootAsync({
  useFactory: async () => ({
    circuitBreaker: { enabled: true, timeout: 2000 },
    retry: { enabled: true, maxRetries: 5 },
  }),
});

2️⃣ Aplicar decoradores en los endpoints

Puedes usar los decoradores que provee la librería en tus controladores de NestJS:

import { Controller, Get } from "@nestjs/common";
import {
  UseCircuitBreaker,
  UseRetry,
  UseTimeout,
  UseFallback,
} from "resilience-kit";

@Controller("demo")
export class DemoController {
  @Get("retry")
  @UseRetry()
  getWithRetry() {
    throw new Error("Forzando error para reintento");
  }

  @Get("timeout")
  @UseTimeout()
  async getWithTimeout() {
    return new Promise((resolve) =>
      setTimeout(() => resolve("Respuesta tardía"), 5000)
    );
  }

  @Get("circuit")
  @UseCircuitBreaker()
  getWithCircuitBreaker() {
    if (Math.random() < 0.7) {
      throw new Error("Random Failure");
    }
    return "Success!";
  }

  @Get("fallback")
  @UseFallback()
  getWithFallback() {
    throw new Error("Forzamos error para usar fallbackMethod");
  }
}

Nota: Cuando un patrón no está habilitado (enabled: false), el interceptor simplemente no hace nada.

Uso a nivel de servicios

Puedes usar el patron que quieras a nivel de servicio como:

import { Injectable } from '@nestjs/common';
import { RetryService } from 'resilience-kit';

@Injectable()
export class AppService {
  constructor(private readonly retryService: RetryService) {} // Inyectamos el servicio

  async doOperationWithRetry(): Promise<string> {
    // "execute()" reintentará tu función si falla
    return this.retryService.execute(async () => {
      // Lógica que podría fallar
      if (Math.random() < 0.7) {
        throw new Error('Random error');
      }
      return 'Success after random error!';
    });
  }
}

Nota: Al usarlo de esta manera se vuelve más repetitivo pero se tiene un control más exacto. En cambio por Decoradorador/Interceptor separa la lógica de los patrones con la lógica de negocio (volviendo el código mas limpio). Pero se tiene menos control, ya que es en tiempo de Request.

🔗 Uso en cadena

Puedes aplicar múltiples patrones, todos a la vez y simultáneamente con un solo decorador (Aplicandolos en orden lógico):

@Get('all-patterns')
@UseResilienceChain() // Aplica Timeout, Retry, Circuit Breaker, Fallback, etc.
myEndpoint() {
  // Lógica del endpoint
}

O puedes habilitar los que desees usar de la siguiente manera:

  @Get('timeout-retry')
  @UseResilienceChain({ timeout: true, retry: true }) // Solo aplica Retry y TimeOut
  getTimeoutAndRetry() {
    return this.testService.mightFailRandomly();
  }

📡 Logs

También puedes loggear la configuración inicial al arrancar la aplicación:

  imports: [
    ResilienceModule.forRoot({
      logOnStartup: true, // Para loggear apenas arranca
      circuitBreaker: {
        enabled: true,
        timeout: 2000,
        errorThresholdPercentage: 50,
        resetTimeout: 3000,
      },
    }),
  ],

🔍 Ver configuraciones

Puedes ver las configuraciones según un patrón a través del servicio, de la siguiente manera:

Module({
  imports: [
    ResilienceModule.forRoot({
      fallback: {
        enabled: true,
        fallbackMethod: sayHello,
      },
    }),
  ],
  providers: [ResilienceService], // Proveer el servicio
})

Para utilizarlo dentro de la aplicación, se inyecta en el constructor y se utiliza de la siguiente manera:

import { Injectable } from '@nestjs/common';
import { ResilienceService } from 'resilience-kit';

export class AppService {
  private attemptCount = 0;

  constructor(private readonly resilienceService: ResilienceService) {}

  alwaysFails() {
    console.log(this.rs.getCircuitBreakerOptions()); // Obtiene en fallback
    throw new Error('I always fail!');
  }

  /** output:
    {
      enabled: true,
      errorThresholdPercentage: 50,
      resetTimeout: 5000,
      timeout: 1000
    }
   * /

📌 Uso básico en NodeJS con Express

1️⃣ Importa resilience kit en tu proyecto usando el patron que quieras y con la configuración que quieras:

const { RetryService, FallbackService } = require("resilience-kit");

const retryService = new RetryService({
  enabled: true,
  maxRetries: 3,
  delayMs: 500,
});

const fallbackService = new FallbackService({
  enabled: true,
  fallbackMethod: () => ({ message: "Fallback used2!" }),
});

2️⃣ Aplica en tus EndPoints, para usar solo debes ejecutar los métodos a traves de sus instancias:

app.get("/test/fallback", (req, res) => {
  try {
    // Forzamos error
    throw new Error("Something failed");
  } catch (err) {
    // Llamamos a fallback
    const fallbackValue = fallbackService.executeFallback();
    res.json({ data: fallbackValue });
  }
});

app.get("/test/retry", async (req, res) => {
  try {
    const result = await retryService.execute(() => {
      // Lógica que falla
      if (Math.random() < 0.7) throw new Error("Random fail");
      return "Success after retry!";
    });
    res.json({ data: result });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

📜 Licencia

Este proyecto se distribuye bajo la licencia MIT. Puedes usarlo libremente en entornos personales y comerciales.

1.0.3

4 months ago

1.0.2

4 months ago

1.0.1

4 months ago

1.0.0

4 months ago