Resilience
Build fault-tolerant applications with Ductape's resilience features including quotas for load distribution, fallbacks for automatic failover, and health monitoring.
When using a publishable key (frontend), include session (token from your backend) in every request.
Quotas
Quotas distribute load across multiple providers based on weighted distribution, with health-aware provider selection.
Running Quota Operations
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
accessKey: 'your-access-key',
product: 'your-product',
env: 'prd'
});
// Execute operation with weighted provider distribution
const sessionToken = getSessionFromYourBackend();
const result = await ductape.resilience.quotas.run({
tag: 'payment-quota',
input: {
amount: 1000,
currency: 'usd',
customer: 'cus_123'
},
session: sessionToken,
});
console.log(`Processed by ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
console.log(`Healthy provider: ${result.wasHealthy}`);
console.log(`Retries used: ${result.retriesUsed}`);
Checking Quota Status
// Check and consume quota
const quotaResult = await ductape.resilience.quotas.check({
tag: 'api-calls',
identifier: 'user-123',
amount: 1
});
if (!quotaResult.allowed) {
console.log('Rate limited!');
console.log(`Remaining: ${quotaResult.remaining}`);
console.log(`Resets at: ${new Date(quotaResult.resetsAt)}`);
}
Getting Quota Status
// Get quota status without consuming
const status = await ductape.resilience.quotas.status({
tag: 'api-calls',
identifier: 'user-123'
});
console.log(`Used: ${status.used} / ${status.limit}`);
console.log(`Remaining: ${status.remaining}`);
Subscribing to Quota Changes
// Connect first for real-time features
await ductape.connect();
// Subscribe to quota changes
const subscription = ductape.resilience.quotas.subscribe(
{
tag: 'api-calls',
identifier: 'user-123'
},
(events) => {
events.forEach(event => {
console.log(`Quota changed: ${event.remaining} remaining`);
});
}
);
// Unsubscribe when done
subscription.unsubscribe();
Fallbacks
Fallbacks provide automatic failover across multiple providers, trying them sequentially until one succeeds.
Running Fallback Operations
// Execute with automatic failover
const result = await ductape.resilience.fallbacks.run({
tag: 'email-fallback',
input: {
to: 'user@example.com',
subject: 'Welcome!',
body: 'Thank you for signing up'
}
});
console.log(`Email sent via ${result.provider}`);
console.log(`Latency: ${result.latency}ms`);
console.log(`Providers tried: ${result.providersTried}`);
console.log(`Used healthy provider: ${result.wasHealthy}`);
Fallback with Complex Input
// Send payment with fallback providers
const paymentResult = await ductape.resilience.fallbacks.run({
tag: 'payment-fallback',
input: {
amount: 5000,
currency: 'usd',
customer: {
id: 'cus_123',
email: 'customer@example.com'
},
metadata: {
order_id: 'ord_456',
source: 'web'
}
}
});
if (paymentResult.data.success) {
console.log('Payment processed successfully');
console.log(`Transaction ID: ${paymentResult.data.transactionId}`);
}
Health Monitoring
Monitor the health status of your services and providers.
Checking Health Status
// Get health status
const health = await ductape.resilience.health.status({
tag: 'payment-service'
});
console.log(`Overall status: ${health.status}`); // 'healthy' | 'degraded' | 'unhealthy'
console.log(`Last check: ${health.lastCheck}`);
// Check individual probes
health.probes.forEach(probe => {
console.log(`${probe.name}: ${probe.status}`);
if (probe.latency) {
console.log(` Latency: ${probe.latency}ms`);
}
if (probe.error) {
console.log(` Error: ${probe.error}`);
}
});
Subscribing to Health Changes
// Connect first for real-time features
await ductape.connect();
// Subscribe to health status changes
const healthSubscription = ductape.resilience.health.subscribe(
{
tag: 'payment-service'
},
(events) => {
events.forEach(event => {
console.log(`Health changed to ${event.status}`);
if (event.status === 'unhealthy') {
// Alert your team or trigger automated recovery
console.warn('Service is unhealthy!');
}
});
}
);
// Unsubscribe when done
healthSubscription.unsubscribe();
Complete Example: Payment Processing
import { Ductape } from '@ductape/client';
const ductape = new Ductape({
accessKey: 'your-access-key',
product: 'payment-app',
env: 'prd'
});
async function processPayment(userId: string, amount: number) {
try {
// First, check if user has quota available
const quota = await ductape.resilience.quotas.check({
tag: 'payment-quota',
identifier: userId,
amount: 1
});
if (!quota.allowed) {
throw new Error(`Rate limit exceeded. Resets at ${new Date(quota.resetsAt)}`);
}
// Check health of payment service before proceeding
const health = await ductape.resilience.health.status({
tag: 'payment-providers'
});
if (health.status === 'unhealthy') {
throw new Error('Payment service is currently unavailable');
}
// Process payment with automatic failover
const result = await ductape.resilience.fallbacks.run({
tag: 'payment-fallback',
input: {
userId,
amount,
currency: 'usd'
}
});
console.log(`Payment processed by ${result.provider} in ${result.latency}ms`);
return result.data;
} catch (error) {
console.error('Payment failed:', error);
throw error;
}
}
// Process a payment
processPayment('user-123', 1000);
Type Safety
All resilience methods are fully typed:
interface PaymentResult {
transactionId: string;
status: 'success' | 'failed';
processorFee: number;
}
// Type-safe quota run
const result = await ductape.resilience.quotas.run<PaymentResult>({
tag: 'payment-quota',
input: { amount: 1000 }
});
// TypeScript knows result.data is PaymentResult
console.log(result.data.transactionId);
// Type-safe fallback run
const fallbackResult = await ductape.resilience.fallbacks.run<PaymentResult>({
tag: 'payment-fallback',
input: { amount: 1000 }
});
console.log(fallbackResult.data.status); // TypeScript knows this is 'success' | 'failed'
Error Handling
try {
const result = await ductape.resilience.quotas.run({
tag: 'email-quota',
input: { to: 'user@example.com', subject: 'Hello' }
});
console.log('Email sent successfully');
} catch (error) {
if (error.message.includes('No providers available')) {
console.error('All email providers are down');
} else if (error.message.includes('All retries failed')) {
console.error('Maximum retries exceeded');
} else {
console.error('Unexpected error:', error);
}
}
Best Practices
- Check Quotas First: Always check quota availability before expensive operations
- Monitor Health: Subscribe to health changes to proactively handle degraded services
- Use Fallbacks for Critical Operations: Implement fallbacks for operations that must succeed
- Handle Errors Gracefully: Always handle quota exceeded and service unavailable scenarios
- Log Provider Information: Track which providers are being used for debugging and optimization
- Set Appropriate Timeouts: Configure reasonable timeouts for your use case
- Test Failover Scenarios: Regularly test that your fallback chains work as expected