Fallbacks
Automatically failover to backup providers when your primary provider fails.
Overview
Fallbacks provide sequential failover - try the primary provider first, and if it fails, automatically try the next one in line. This ensures your operations complete even when individual providers have issues.
Defining a Fallback
- TypeScript
- Java
- Go
- .NET
const fallback = await resilience.fallback.define({
tag: 'payment-fallback',
name: 'Payment Provider Fallback',
description: 'Failover between payment providers',
input: {
amount: { type: 'number' },
currency: { type: 'string' },
card_token: { type: 'string' },
},
handler: async (ctx) => {
// Primary provider - tried first
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
// Fallback provider - tried if primary fails
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
intent: 'CAPTURE',
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
},
});
Map<String, Object> fallback = resilience.fallback.define(Map.of(
"tag", "payment-fallback",
"name", "Payment Provider Fallback",
"description", "Failover between payment providers",
input: Map.of(
amount: Map.of( "type", "number" ),
currency: Map.of( "type", "string" ),
card_token: Map.of( "type", "string" )
),
handler: async (ctx) => Map.of(
// Primary provider - tried first
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge')
.mapInput((input) => (Map.of(
body: Map.of(
amount: input.amount,
currency: input.currency,
source: input.card_token
)
)))
.mapOutput((res) => (Map.of(
chargeId: res.id,
status: res.status
)))
.retries(2);
// Fallback provider - tried if primary fails
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment')
.mapInput((input) => (Map.of(
body: Map.of(
amount: Map.of( value: input.amount / 100, currency: input.currency ),
"intent", "CAPTURE"
)
)))
.mapOutput((res) => (Map.of(
chargeId: res.id,
status: res.status
)))
.retries(2);
)
));
fallback := resilience.fallback.define({
"tag": "payment-fallback",
"name": "Payment Provider Fallback",
"description": "Failover between payment providers",
input: {
amount: { "type": "number" },
currency: { "type": "string" },
card_token: { "type": "string" },
},
handler: async (ctx) => {
// Primary provider - tried first
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
// Fallback provider - tried if primary fails
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
"intent": "CAPTURE",
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
},
});
var fallback = await resilience.fallback.define({
["tag"] = "payment-fallback",
["name"] = "Payment Provider Fallback",
["description"] = "Failover between payment providers",
input: {
amount: { ["type"] = "number" },
currency: { ["type"] = "string" },
card_token: { ["type"] = "string" },
},
handler: async (ctx) => {
// Primary provider - tried first
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
// Fallback provider - tried if primary fails
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
["intent"] = "CAPTURE",
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
},
});
Primary vs Fallback
Primary Provider
The first provider to try. Define with ctx.primary():
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge');
Fallback Providers
Backup providers tried in order if primary fails. Define with ctx.fallback():
- TypeScript
- Java
- Go
- .NET
// First fallback
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment');
// Second fallback
ctx.fallback('square')
.healthcheck('square-health')
.app('square-app')
.action('create-payment');
// First fallback
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment');
// Second fallback
ctx.fallback('square')
.healthcheck('square-health')
.app('square-app')
.action('create-payment');
// First fallback
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment');
// Second fallback
ctx.fallback('square')
.healthcheck('square-health')
.app('square-app')
.action('create-payment');
// First fallback
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment');
// Second fallback
ctx.fallback('square')
.healthcheck('square-health')
.app('square-app')
.action('create-payment');
Provider Configuration
Healthcheck
Link a healthcheck to skip unhealthy providers:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
Target Types
App Action
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.app('stripe-app')
.action('charge');
Database
- TypeScript
- Java
- Go
- .NET
ctx.primary('primary-db')
.database('main-db')
.action('write');
ctx.primary('primary-db')
.database('main-db')
.action('write');
ctx.primary('primary-db')
.database('main-db')
.action('write');
ctx.primary('primary-db')
.database('main-db')
.action('write');
Feature
- TypeScript
- Java
- Go
- .NET
ctx.primary('standard-flow')
.feature('order-processing')
.input({ priority: 'high' });
ctx.primary('standard-flow')
.feature('order-processing')
.input(Map.of( "priority", "high" ));
ctx.primary('standard-flow')
.feature('order-processing')
.input({ "priority": "high" });
ctx.primary('standard-flow')
.feature('order-processing')
.input({ ["priority"] = "high" });
Notification
- TypeScript
- Java
- Go
- .NET
ctx.primary('sendgrid')
.notification('transactional-email')
.event('send');
ctx.primary('sendgrid')
.notification('transactional-email')
.event('send');
ctx.primary('sendgrid')
.notification('transactional-email')
.event('send');
ctx.primary('sendgrid')
.notification('transactional-email')
.event('send');
Input/Output Mapping
Transform input/output for each provider:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status,
provider: 'stripe',
}));
ctx.fallback('paypal')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status === 'COMPLETED' ? 'succeeded' : response.status,
provider: 'paypal',
}));
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.mapInput((input) => (Map.of(
body: Map.of(
amount: input.amount,
currency: input.currency,
source: input.card_token
)
)))
.mapOutput((response) => (Map.of(
chargeId: response.id,
status: response.status,
"provider", "stripe"
)));
ctx.fallback('paypal')
.app('paypal-app')
.action('payment')
.mapInput((input) => (Map.of(
body: Map.of(
amount: Map.of( value: input.amount / 100, currency: input.currency )
)
)))
.mapOutput((response) => (Map.of(
chargeId: response.id,
status: response.status === 'COMPLETED' ? 'succeeded' : response.status,
"provider", "paypal"
)));
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status,
"provider": "stripe",
}));
ctx.fallback('paypal')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status === 'COMPLETED' ? 'succeeded' : response.status,
"provider": "paypal",
}));
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status,
["provider"] = "stripe",
}));
ctx.fallback('paypal')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status === 'COMPLETED' ? 'succeeded' : response.status,
["provider"] = "paypal",
}));
Retries
Configure retries per provider:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.retries(3); // Retry 3 times before trying fallback
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.retries(3); // Retry 3 times before trying fallback
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.retries(3); // Retry 3 times before trying fallback
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.retries(3); // Retry 3 times before trying fallback
Check Interval
Time between health checks:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.checkInterval(10000); // Check health every 10 seconds
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.checkInterval(10000); // Check health every 10 seconds
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.checkInterval(10000); // Check health every 10 seconds
ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.checkInterval(10000); // Check health every 10 seconds
Running Fallbacks
Synchronous Run
- TypeScript
- Java
- Go
- .NET
const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: {
amount: 5000,
currency: 'usd',
card_token: 'tok_visa',
},
});
Map<String, Object> result = resilience.fallback.run(Map.of(
"tag", "payment-fallback",
input: Map.of(
"amount", 5000,
"currency", "usd",
"card_token", "tok_visa"
)
));
result := resilience.fallback.run({
"tag": "payment-fallback",
input: {
"amount": 5000,
"currency": "usd",
"card_token": "tok_visa",
},
});
var result = await resilience.fallback.run({
["tag"] = "payment-fallback",
input: {
["amount"] = 5000,
["currency"] = "usd",
["card_token"] = "tok_visa",
},
});
With Session
- TypeScript
- Java
- Go
- .NET
const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
session: {
tag: 'checkout-session',
token: 'session-123'
},
});
Map<String, Object> result = resilience.fallback.run(Map.of(
"tag", "payment-fallback",
input: Map.of( "amount", 5000, "currency", "usd", "card_token", "tok_visa" ),
session: Map.of(
"tag", "checkout-session",
"token", "session-123"
)
));
result := resilience.fallback.run({
"tag": "payment-fallback",
input: { "amount": 5000, "currency": "usd", "card_token": "tok_visa" },
session: {
"tag": "checkout-session",
"token": "session-123"
},
});
var result = await resilience.fallback.run({
["tag"] = "payment-fallback",
input: { ["amount"] = 5000, ["currency"] = "usd", ["card_token"] = "tok_visa" },
session: {
["tag"] = "checkout-session",
["token"] = "session-123"
},
});
With Caching
- TypeScript
- Java
- Go
- .NET
const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
cache: 'payment-cache',
});
Map<String, Object> result = resilience.fallback.run(Map.of(
"tag", "payment-fallback",
input: Map.of( "amount", 5000, "currency", "usd", "card_token", "tok_visa" ),
"cache", "payment-cache"
));
result := resilience.fallback.run({
"tag": "payment-fallback",
input: { "amount": 5000, "currency": "usd", "card_token": "tok_visa" },
"cache": "payment-cache",
});
var result = await resilience.fallback.run({
["tag"] = "payment-fallback",
input: { ["amount"] = 5000, ["currency"] = "usd", ["card_token"] = "tok_visa" },
["cache"] = "payment-cache",
});
Dispatching Fallbacks
Schedule fallback execution for later:
- TypeScript
- Java
- Go
- .NET
// Dispatch with delay
const job = await resilience.fallback.dispatch({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
schedule: {
delay: 5000, // Run after 5 seconds
},
});
// Dispatch at specific time
const job = await resilience.fallback.dispatch({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
schedule: {
at: new Date('2024-12-25T00:00:00Z'),
},
});
// Dispatch with delay
Map<String, Object> job = resilience.fallback.dispatch(Map.of(
"tag", "payment-fallback",
input: Map.of( "amount", 5000, "currency", "usd", "card_token", "tok_visa" ),
schedule: Map.of(
"delay", 5000, // Run after 5 seconds
)
));
// Dispatch at specific time
Map<String, Object> job = resilience.fallback.dispatch(Map.of(
"tag", "payment-fallback",
input: Map.of( "amount", 5000, "currency", "usd", "card_token", "tok_visa" ),
schedule: Map.of(
at: new Date('2024-12-"25T00", 00:00Z')
)
));
// Dispatch with delay
job := resilience.fallback.dispatch({
"tag": "payment-fallback",
input: { "amount": 5000, "currency": "usd", "card_token": "tok_visa" },
schedule: {
"delay": 5000, // Run after 5 seconds
},
});
// Dispatch at specific time
job := resilience.fallback.dispatch({
"tag": "payment-fallback",
input: { "amount": 5000, "currency": "usd", "card_token": "tok_visa" },
schedule: {
at: new Date('2024-12-"25T00": 00:00Z'),
},
});
// Dispatch with delay
var job = await resilience.fallback.dispatch({
["tag"] = "payment-fallback",
input: { ["amount"] = 5000, ["currency"] = "usd", ["card_token"] = "tok_visa" },
schedule: {
["delay"] = 5000, // Run after 5 seconds
},
});
// Dispatch at specific time
var job = await resilience.fallback.dispatch({
["tag"] = "payment-fallback",
input: { ["amount"] = 5000, ["currency"] = "usd", ["card_token"] = "tok_visa" },
schedule: {
at: new Date('2024-12-["25T00"] = 00:00Z'),
},
});
Managing Fallbacks
Create
- TypeScript
- Java
- Go
- .NET
await resilience.fallback.create(productId, fallback);
resilience.fallback.create(productId, fallback);
resilience.fallback.create(productId, fallback);
await resilience.fallback.create(productId, fallback);
Fetch
- TypeScript
- Java
- Go
- .NET
const fb = await resilience.fallback.fetch(productId, 'payment-fallback');
Map<String, Object> fb = resilience.fallback.fetch(productId, 'payment-fallback');
fb := resilience.fallback.fetch(productId, 'payment-fallback');
var fb = await resilience.fallback.fetch(productId, 'payment-fallback');
Fetch All
- TypeScript
- Java
- Go
- .NET
const fallbacks = await resilience.fallback.fetchAll(productId);
Map<String, Object> fallbacks = resilience.fallback.fetchAll(productId);
fallbacks := resilience.fallback.fetchAll(productId);
var fallbacks = await resilience.fallback.fetchAll(productId);
Update
- TypeScript
- Java
- Go
- .NET
await resilience.fallback.update(productId, 'payment-fallback', {
input: { amount: { type: 'number' }, currency: { type: 'string' } },
handler: async (ctx) => {
ctx.primary('stripe').app('stripe-app').action('charge');
ctx.fallback('paypal').app('paypal-app').action('payment');
ctx.fallback('square').app('square-app').action('create-payment');
},
});
resilience.fallback.update(productId, 'payment-fallback', Map.of(
input: Map.of( amount: Map.of( "type", "number" ), currency: Map.of( "type", "string" ) ),
handler: async (ctx) => Map.of(
ctx.primary('stripe').app('stripe-app').action('charge');
ctx.fallback('paypal').app('paypal-app').action('payment');
ctx.fallback('square').app('square-app').action('create-payment');
)
));
resilience.fallback.update(productId, 'payment-fallback', {
input: { amount: { "type": "number" }, currency: { "type": "string" } },
handler: async (ctx) => {
ctx.primary('stripe').app('stripe-app').action('charge');
ctx.fallback('paypal').app('paypal-app').action('payment');
ctx.fallback('square').app('square-app').action('create-payment');
},
});
await resilience.fallback.update(productId, 'payment-fallback', {
input: { amount: { ["type"] = "number" }, currency: { ["type"] = "string" } },
handler: async (ctx) => {
ctx.primary('stripe').app('stripe-app').action('charge');
ctx.fallback('paypal').app('paypal-app').action('payment');
ctx.fallback('square').app('square-app').action('create-payment');
},
});
Delete
- TypeScript
- Java
- Go
- .NET
await resilience.fallback.delete(productId, 'payment-fallback');
resilience.fallback.delete(productId, 'payment-fallback');
resilience.fallback.delete(productId, 'payment-fallback');
await resilience.fallback.delete(productId, 'payment-fallback');
How Fallback Selection Works
- Check primary health: Is the primary provider healthy?
- Try primary: If healthy, execute the primary provider
- On failure: If primary fails (after retries), move to next fallback
- Check fallback health: Is the fallback provider healthy?
- Try fallback: If healthy, execute the fallback
- Repeat: Continue until success or no more fallbacks
Fallbacks vs Quotas
| Feature | Fallbacks | Quotas |
|---|---|---|
| Strategy | Sequential failover | Weighted distribution |
| Use case | Backup providers | Load balancing |
| Selection | Try in order | Random by weight |
| Traffic | 100% to primary | Split across providers |
When to Use Fallbacks
- You have a preferred provider
- You want guaranteed execution order
- Cost varies significantly between providers
When to Use Quotas
- You want to distribute load
- All providers are equally capable
- You want to test new providers with limited traffic
Best Practices
Always Use Healthchecks
Ensure you skip unhealthy providers:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');
Normalize Output
Use mapOutput for consistent responses:
- TypeScript
- Java
- Go
- .NET
// Both return { chargeId, status, provider }
ctx.primary('stripe')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
provider: 'stripe'
}));
ctx.fallback('paypal')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status === 'COMPLETED' ? 'succeeded' : res.status,
provider: 'paypal'
}));
// Both return Map.of( chargeId, status, provider )
ctx.primary('stripe')
.mapOutput((res) => (Map.of(
chargeId: res.id,
status: res.status,
"provider", "stripe"
)));
ctx.fallback('paypal')
.mapOutput((res) => (Map.of(
chargeId: res.id,
status: res.status === 'COMPLETED' ? 'succeeded' : res.status,
"provider", "paypal"
)));
// Both return { chargeId, status, provider }
ctx.primary('stripe')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
"provider": "stripe"
}));
ctx.fallback('paypal')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status === 'COMPLETED' ? 'succeeded' : res.status,
"provider": "paypal"
}));
// Both return { chargeId, status, provider }
ctx.primary('stripe')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
["provider"] = "stripe"
}));
ctx.fallback('paypal')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status === 'COMPLETED' ? 'succeeded' : res.status,
["provider"] = "paypal"
}));
Set Appropriate Retries
Balance reliability vs latency:
- TypeScript
- Java
- Go
- .NET
// Primary: more retries (it's preferred)
ctx.primary('stripe')
.retries(3);
// Fallbacks: fewer retries (already in fallback mode)
ctx.fallback('paypal')
.retries(1);
// Primary: more retries (it's preferred)
ctx.primary('stripe')
.retries(3);
// Fallbacks: fewer retries (already in fallback mode)
ctx.fallback('paypal')
.retries(1);
// Primary: more retries (it's preferred)
ctx.primary('stripe')
.retries(3);
// Fallbacks: fewer retries (already in fallback mode)
ctx.fallback('paypal')
.retries(1);
// Primary: more retries (it's preferred)
ctx.primary('stripe')
.retries(3);
// Fallbacks: fewer retries (already in fallback mode)
ctx.fallback('paypal')
.retries(1);
Order Fallbacks by Preference
Put most reliable/cost-effective fallbacks first:
- TypeScript
- Java
- Go
- .NET
ctx.primary('stripe'); // Best rates
ctx.fallback('paypal'); // Good alternative
ctx.fallback('square'); // Last resort
ctx.primary('stripe'); // Best rates
ctx.fallback('paypal'); // Good alternative
ctx.fallback('square'); // Last resort
ctx.primary('stripe'); // Best rates
ctx.fallback('paypal'); // Good alternative
ctx.fallback('square'); // Last resort
ctx.primary('stripe'); // Best rates
ctx.fallback('paypal'); // Good alternative
ctx.fallback('square'); // Last resort