5 Essential Workflow Patterns for Reliable Systems
Building reliable distributed systems requires more than just good code - you need the right patterns. Here are five essential workflow patterns that will help you build more resilient applications.
1. Saga Pattern
The Saga pattern helps manage distributed transactions across multiple services.
async function purchaseWorkflow(ctx, orderData) {
try {
await ctx.executeActivity('reserveInventory', orderData)
await ctx.executeActivity('chargePayment', orderData)
await ctx.executeActivity('shipOrder', orderData)
} catch (error) {
// Compensating actions
await ctx.executeActivity('releaseInventory', orderData)
await ctx.executeActivity('refundPayment', orderData)
}
}
Use When: You need distributed transactions across multiple services.
2. Human-in-the-Loop
Some processes require human approval or intervention.
async function approvalWorkflow(ctx, requestData) {
// Auto-approve small amounts
if (requestData.amount < 1000) {
return await ctx.executeActivity('processRequest', requestData)
}
// Require human approval for larger amounts
const approval = await ctx.executeActivity('requestApproval', requestData)
if (approval.approved) {
return await ctx.executeActivity('processRequest', requestData)
} else {
throw new Error('Request denied')
}
}
Use When: Manual approval or review is required in your process.
3. Retry with Exponential Backoff
Handle transient failures gracefully with smart retry logic.
const retryOptions = {
initialInterval: '1s',
maximumInterval: '60s',
backoffCoefficient: 2.0,
maximumAttempts: 10
}
async function reliableProcessing(ctx, data) {
return await ctx.executeActivity('processData', data, {
retry: retryOptions
})
}
Use When: Dealing with external services that may have temporary failures.
4. Fan-Out/Fan-In
Process multiple items in parallel, then combine results.
async function batchProcessingWorkflow(ctx, items) {
// Fan-out: process items in parallel
const promises = items.map(item =>
ctx.executeActivity('processItem', item)
)
// Fan-in: wait for all to complete
const results = await Promise.all(promises)
// Combine results
return await ctx.executeActivity('combineResults', results)
}
Use When: You need to process multiple independent items efficiently.
5. Circuit Breaker
Protect your system from cascading failures.
async function protectedWorkflow(ctx, data) {
const circuitBreaker = await ctx.getCircuitBreakerState('external-service')
if (circuitBreaker.isOpen()) {
// Use fallback or cached data
return await ctx.executeActivity('getFallbackData', data)
}
try {
return await ctx.executeActivity('callExternalService', data)
} catch (error) {
await ctx.executeActivity('recordFailure', 'external-service')
throw error
}
}
Use When: Calling external services that might become unavailable.
Putting It All Together
These patterns can be combined to create robust workflows:
async function robustEcommerceWorkflow(ctx, order) {
// Circuit breaker for inventory check
const inventoryAvailable = await checkInventoryWithCircuitBreaker(ctx, order)
if (!inventoryAvailable) {
throw new Error('Inventory unavailable')
}
// Saga pattern for the purchase process
try {
await processOrderSaga(ctx, order)
// Fan-out for notifications
await sendNotifications(ctx, order)
} catch (error) {
await compensateOrder(ctx, order)
throw error
}
}
Best Practices
- Keep Activities Idempotent: Activities should be safe to retry
- Use Timeouts: Always set appropriate timeouts for activities
- Monitor Everything: Track workflow metrics and failures
- Test Failure Scenarios: Simulate failures to validate your patterns
Ready to Implement?
These patterns are built into Sequencely’s platform, making it easy to implement reliable workflows without the complexity.
Want to learn more about workflow patterns? Join our community or reach out to our team for personalized guidance.