Skip to main content

Resilience Patterns in BFAPIClient

This document provides a guide to the resilience patterns available in BFAPIClient.

Circuit Breaker Patternโ€‹

The circuit breaker pattern is a design pattern used to detect failures and prevent them from constantly recurring. It wraps a protected function call in a circuit breaker object that monitors for failures. Once the failures reach a certain threshold, the circuit breaker trips and all further calls return with an error without executing the function. After a timeout, the circuit breaker allows a limited number of test calls to pass through. If those succeed, the circuit is closed again and calls proceed normally.

Basic Usageโ€‹

import { CircuitBreaker, CircuitState } from '@bluefly/api-client';

// Create a circuit breaker
const circuitBreaker = new CircuitBreaker({
threshold: 5, // Number of failures before opening the circuit
resetTimeout: 30000, // Time to wait before allowing requests again (ms)
successThreshold: 2, // Number of successes needed to close the circuit again
recordEvents: true // Whether to record metrics
});

// Check if a service is available
if (circuitBreaker.isAvailable('my-endpoint')) {
try {
// Make the request
const result = await apiClient.get('/my-endpoint');

// Record success
circuitBreaker.recordSuccess('my-endpoint');

return result;
} catch (error) {
// Record failure
circuitBreaker.recordFailure('my-endpoint', 'default', error);
throw error;
}
} else {
throw new Error('Circuit is open for my-endpoint');
}

Simplified Usage with Execute Methodโ€‹

import [CircuitBreaker] from '@bluefly/api-client';

const circuitBreaker = new CircuitBreaker();

try {
// Execute function with circuit breaker protection
const result = await circuitBreaker.execute(
() => apiClient.get('/my-endpoint'),
'my-endpoint'
);
return result;
} catch (error) {
// Handle error (circuit might be open)
if (error.code === 'CIRCUIT_OPEN') {
console.log('Circuit is open, try again later');
}
throw error;
}

With Telemetryโ€‹

import { CircuitBreaker, TelemetryClient } from '@bluefly/api-client';

// Create a telemetry client
const telemetry = new TelemetryClient({
serviceName: 'my-service',
environment: 'production'
});

// Create a circuit breaker with telemetry
const circuitBreaker = new CircuitBreaker({
threshold: 5,
resetTimeout: 30000,
telemetry,
onStateChange: (service, endpoint, from, to) => {
console.log(`Circuit state changed for service-name:/api/endpoint from [from] to [to]`);
}
});

// Use the circuit breaker
const result = await circuitBreaker.execute(
() => apiClient.get('/my-endpoint'),
'my-endpoint'
);

Circuit Breaker Statesโ€‹

The circuit breaker has three states:

  1. CLOSED: Normal operation, requests flow through.
  2. OPEN: Circuit is open, requests are blocked to prevent cascading failures.
  3. HALF_OPEN: Testing if the service is healthy again by allowing a limited number of requests.

You can check the current state of a circuit:

const state = circuitBreaker.getState('my-endpoint');
console.log(`Circuit state: [state]`); // CLOSED, OPEN, or HALF_OPEN

Circuit Breaker Metricsโ€‹

You can get detailed metrics for a circuit:

const metrics = circuitBreaker.getMetrics('my-endpoint');
console.log(`
State: [metrics.state]
Failures: [metrics.failures]
Successes: [metrics.successes]
Last Failure: [new Date(metrics.lastFailure).toISOString()]
Last Success: [new Date(metrics.lastSuccess).toISOString()]
Last State Change: [new Date(metrics.lastStateChange).toISOString()]
`);

Integration with Error Handlingโ€‹

The circuit breaker integrates with the BFAPIClient error system:

import { CircuitBreaker, isErrorCode, ErrorCode } from '@bluefly/api-client';

const circuitBreaker = new CircuitBreaker();

try {
const result = await circuitBreaker.execute(
() => apiClient.get('/my-endpoint'),
'my-endpoint'
);
return result;
} catch (error) {
if (isErrorCode(error, ErrorCode.CIRCUIT_OPEN)) {
// Circuit is open
return fallbackResponse();
}
throw error;
}

Best Practicesโ€‹

  1. Group related endpoints: Use the service parameter to group related endpoints.
  2. Set appropriate thresholds: The threshold should be high enough to ignore occasional failures but low enough to prevent cascading failures.
  3. Set appropriate reset timeouts: The timeout should be long enough for the service to recover but short enough to restore service quickly.
  4. Use telemetry: Always enable telemetry to monitor circuit breaker behavior.
  5. Implement fallbacks: Have fallback mechanisms ready when circuits are open.
  6. Monitor circuit breaker events: Set up alerts for circuit state changes.

Advanced Configurationโ€‹

import { CircuitBreaker, TelemetryClient } from '@bluefly/api-client';

const circuitBreaker = new CircuitBreaker({
// Basic settings
enabled: true, // Whether the circuit breaker is enabled
threshold: 5, // Failure threshold
resetTimeout: 30000, // Reset timeout in ms
successThreshold: 2, // Success threshold for half-open state

// Telemetry and events
recordEvents: true, // Whether to record events
telemetry: new TelemetryClient({
serviceName: 'my-service'
}),

// State change callback
onStateChange: (service, endpoint, from, to) => {
console.log(`Circuit state changed for service-name:/api/endpoint from [from] to [to]`);

// Notify operations team
if (to === 'OPEN') {
notifyOperations(`Circuit opened for service-name:/api/endpoint`);
}
}
});

Multi-Service Configurationโ€‹

import [CircuitBreaker] from '@bluefly/api-client';

const circuitBreaker = new CircuitBreaker();

// Use different service names for different services
await circuitBreaker.execute(() => userService.getUser(id), 'getUser', 'user-service');
await circuitBreaker.execute(() => orderService.getOrder(id), 'getOrder', 'order-service');

// Check if services are available
const userServiceAvailable = circuitBreaker.isAvailable('getUser', 'user-service');
const orderServiceAvailable = circuitBreaker.isAvailable('getOrder', 'order-service');

Combining with Retry Patternsโ€‹

The circuit breaker pattern works well with retry patterns:

import { 
CircuitBreaker,
RestClient,
createResilientRetryInterceptor,
isErrorCode,
ErrorCode
} from '@bluefly/api-client';

// Create client with retry
const client = new RestClient({
baseUrl: 'https://api.example.com'
});

// Add retry interceptor
client.addErrorInterceptor(
createResilientRetryInterceptor({
maxRetries: 3,
retryableStatusCodes: [500, 502, 503, 504]
})
);

// Create circuit breaker
const circuitBreaker = new CircuitBreaker();

// Use both patterns together
async function getData(id) {
try {
return await circuitBreaker.execute(
() => client.get(`/data/item`),
`getData-item`
);
} catch (error) {
if (isErrorCode(error, ErrorCode.CIRCUIT_OPEN)) {
return getFallbackData(id);
}
throw error;
}
}

The combination of circuit breaker and retry patterns creates a robust resilience strategy.