Initial commit
This commit is contained in:
235
templates/workflow-with-retries.ts
Normal file
235
templates/workflow-with-retries.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Workflow with Advanced Retry Configuration
|
||||
*
|
||||
* Demonstrates:
|
||||
* - Custom retry limits
|
||||
* - Exponential, linear, and constant backoff
|
||||
* - Step timeouts
|
||||
* - NonRetryableError for terminal failures
|
||||
* - Error handling with try-catch
|
||||
*/
|
||||
|
||||
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
|
||||
import { NonRetryableError } from 'cloudflare:workflows';
|
||||
|
||||
type Env = {
|
||||
MY_WORKFLOW: Workflow;
|
||||
};
|
||||
|
||||
type PaymentParams = {
|
||||
orderId: string;
|
||||
amount: number;
|
||||
customerId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Payment Processing Workflow with Retries
|
||||
*
|
||||
* Handles payment processing with:
|
||||
* - Validation with NonRetryableError
|
||||
* - Retry logic for payment gateway
|
||||
* - Fallback to backup gateway
|
||||
* - Graceful error handling
|
||||
*/
|
||||
export class PaymentWorkflow extends WorkflowEntrypoint<Env, PaymentParams> {
|
||||
async run(event: WorkflowEvent<PaymentParams>, step: WorkflowStep) {
|
||||
const { orderId, amount, customerId } = event.payload;
|
||||
|
||||
// Step 1: Validate input (no retries - fail fast)
|
||||
await step.do(
|
||||
'validate payment request',
|
||||
{
|
||||
retries: {
|
||||
limit: 0 // No retries for validation
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
if (!orderId || !customerId) {
|
||||
throw new NonRetryableError('Missing required fields: orderId or customerId');
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
throw new NonRetryableError(`Invalid amount: ${amount}`);
|
||||
}
|
||||
|
||||
if (amount > 100000) {
|
||||
throw new NonRetryableError(`Amount exceeds limit: ${amount}`);
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
);
|
||||
|
||||
// Step 2: Call primary payment gateway (exponential backoff)
|
||||
let paymentResult;
|
||||
|
||||
try {
|
||||
paymentResult = await step.do(
|
||||
'charge primary payment gateway',
|
||||
{
|
||||
retries: {
|
||||
limit: 5, // Max 5 retry attempts
|
||||
delay: '10 seconds', // Start at 10 seconds
|
||||
backoff: 'exponential' // 10s, 20s, 40s, 80s, 160s
|
||||
},
|
||||
timeout: '2 minutes' // Each attempt times out after 2 minutes
|
||||
},
|
||||
async () => {
|
||||
const response = await fetch('https://primary-payment-gateway.example.com/charge', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
orderId,
|
||||
amount,
|
||||
customerId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Check if error is retryable
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
throw new NonRetryableError('Authentication failed with payment gateway');
|
||||
}
|
||||
|
||||
throw new Error(`Payment gateway error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
transactionId: data.transactionId,
|
||||
status: data.status,
|
||||
gateway: 'primary'
|
||||
};
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Primary gateway failed:', error);
|
||||
|
||||
// Step 3: Fallback to backup gateway (linear backoff)
|
||||
paymentResult = await step.do(
|
||||
'charge backup payment gateway',
|
||||
{
|
||||
retries: {
|
||||
limit: 3,
|
||||
delay: '30 seconds',
|
||||
backoff: 'linear' // 30s, 60s, 90s
|
||||
},
|
||||
timeout: '3 minutes'
|
||||
},
|
||||
async () => {
|
||||
const response = await fetch('https://backup-payment-gateway.example.com/charge', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
orderId,
|
||||
amount,
|
||||
customerId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Backup gateway error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
transactionId: data.transactionId,
|
||||
status: data.status,
|
||||
gateway: 'backup'
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Step 4: Update order status (constant backoff)
|
||||
await step.do(
|
||||
'update order status',
|
||||
{
|
||||
retries: {
|
||||
limit: 10,
|
||||
delay: '5 seconds',
|
||||
backoff: 'constant' // Always 5 seconds between retries
|
||||
},
|
||||
timeout: '30 seconds'
|
||||
},
|
||||
async () => {
|
||||
const response = await fetch(`https://api.example.com/orders/${orderId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
status: 'paid',
|
||||
transactionId: paymentResult.transactionId,
|
||||
gateway: paymentResult.gateway
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update order status');
|
||||
}
|
||||
|
||||
return { updated: true };
|
||||
}
|
||||
);
|
||||
|
||||
// Step 5: Send confirmation (optional - don't fail workflow if this fails)
|
||||
try {
|
||||
await step.do(
|
||||
'send payment confirmation',
|
||||
{
|
||||
retries: {
|
||||
limit: 3,
|
||||
delay: '10 seconds',
|
||||
backoff: 'exponential'
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
await fetch('https://api.example.com/notifications/payment-confirmed', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
orderId,
|
||||
customerId,
|
||||
amount
|
||||
})
|
||||
});
|
||||
|
||||
return { sent: true };
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
// Log but don't fail workflow
|
||||
console.error('Failed to send confirmation:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
orderId,
|
||||
transactionId: paymentResult.transactionId,
|
||||
gateway: paymentResult.gateway,
|
||||
status: 'complete'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(req: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
|
||||
if (url.pathname.startsWith('/favicon')) {
|
||||
return Response.json({}, { status: 404 });
|
||||
}
|
||||
|
||||
// Create payment workflow
|
||||
const instance = await env.MY_WORKFLOW.create({
|
||||
params: {
|
||||
orderId: 'ORD-' + Date.now(),
|
||||
amount: 99.99,
|
||||
customerId: 'CUST-123'
|
||||
}
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
id: instance.id,
|
||||
status: await instance.status()
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user