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:
- CLOSED: Normal operation, requests flow through.
- OPEN: Circuit is open, requests are blocked to prevent cascading failures.
- 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โ
- Group related endpoints: Use the service parameter to group related endpoints.
- Set appropriate thresholds: The threshold should be high enough to ignore occasional failures but low enough to prevent cascading failures.
- Set appropriate reset timeouts: The timeout should be long enough for the service to recover but short enough to restore service quickly.
- Use telemetry: Always enable telemetry to monitor circuit breaker behavior.
- Implement fallbacks: Have fallback mechanisms ready when circuits are open.
- 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.