Workshop 9: Error Handling in Workflows
Building Resilient Distributed Systems
Implement comprehensive error handling strategies in workflows including try-catch blocks, custom exceptions, compensation logic, and graceful degradation patterns
What we want to build
Implement comprehensive error handling strategies in workflows including:
- Try-catch blocks and custom exceptions
- Compensation logic for partial failures
- Circuit breaker patterns to prevent cascading failures
- Graceful degradation for non-critical services
Expecting Result
By the end of this workshop, you'll have:
- ✅ Workflows that handle errors gracefully without failing
- ✅ Custom business exceptions with proper context
- ✅ Compensation logic for partial failures
- ✅ Circuit breaker patterns to prevent cascading failures
Code Steps
Step 1: Custom Business Exceptions
// Define custom exception types
class InsufficientInventoryException(
val productId: String,
val requested: Int,
val available: Int
) : Exception("Insufficient inventory for product $productId: requested $requested, available $available")
class PaymentDeclinedException(
val reason: String,
val errorCode: String
) : Exception("Payment declined: $reason")
class ShippingUnavailableException(
val address: Address,
val reason: String
) : Exception("Shipping unavailable to ${address.zipCode}: $reason")
Specific, contextual exceptions enable better error handling
Step 2: Error Handling Patterns in Workflows
class OrderWorkflowImpl : OrderWorkflow {
override fun processOrder(order: OrderRequest): OrderResult {
val logger = Workflow.getLogger(this::class.java)
try {
// Step 1: Inventory check
val inventoryResult = inventoryActivity.checkAndReserve(order.items)
// Step 2: Payment processing
val paymentResult = try {
paymentActivity.processPayment(order.paymentInfo)
} catch (e: PaymentDeclinedException) {
// Compensate: release inventory
inventoryActivity.releaseReservation(inventoryResult.reservationId)
return OrderResult.failed(
reason = "Payment declined: ${e.reason}",
compensationPerformed = true
)
}
// Continued on next slide...
Error Handling Continued
// Step 3: Shipping arrangement
val shippingResult = try {
shippingActivity.arrangeShipping(order.shippingAddress)
} catch (e: ShippingUnavailableException) {
// Payment succeeded but shipping failed
// Option 1: Refund and release inventory
paymentActivity.refund(paymentResult.transactionId)
inventoryActivity.releaseReservation(inventoryResult.reservationId)
return OrderResult.failed(
reason = "Shipping unavailable: ${e.reason}",
compensationPerformed = true
)
}
return OrderResult.success(
orderId = generateOrderId(),
paymentId = paymentResult.transactionId,
shippingId = shippingResult.trackingId
)
// Continued on next slide...
Global Error Handling
} catch (e: InsufficientInventoryException) {
// Fail fast - no compensation needed
logger.warn("Order failed due to insufficient inventory: ${e.message}")
return OrderResult.failed(
reason = "Product unavailable: ${e.productId}",
compensationPerformed = false
)
} catch (e: Exception) {
// Unexpected error - perform full compensation
logger.error("Unexpected error during order processing: ${e.message}")
// Best effort compensation
try {
compensateOrder(order)
} catch (compensationError: Exception) {
logger.error("Compensation failed: ${compensationError.message}")
}
return OrderResult.failed(
reason = "System error occurred",
compensationPerformed = true
)
}
}
private fun compensateOrder(order: OrderRequest) {
// Implement saga pattern compensation
// Release any resources that were allocated
}
}
Step 3: Circuit Breaker Pattern
class CircuitBreakerWorkflowImpl : CircuitBreakerWorkflow {
override fun processWithCircuitBreaker(request: ProcessingRequest): ProcessingResult {
val circuitBreakerState = getCircuitBreakerState("external-service")
if (circuitBreakerState.isOpen()) {
// Circuit is open, fail fast
return ProcessingResult.failed("External service unavailable (circuit open)")
}
return try {
val result = externalServiceActivity.processRequest(request)
// Success - record for circuit breaker
recordSuccess("external-service")
ProcessingResult.success(result)
// Continued on next slide...
Circuit Breaker Continued
} catch (e: Exception) {
// Failure - record for circuit breaker
recordFailure("external-service", e)
// Check if we should open the circuit
if (shouldOpenCircuit("external-service")) {
openCircuit("external-service")
}
ProcessingResult.failed("External service error: ${e.message}")
}
}
}
Circuit Breaker Benefits:
- ✅ Prevents cascading failures when services are down
- ✅ Fast failure instead of waiting for timeouts
- ✅ Automatic recovery when service becomes available
How to Run
Test error scenarios:
// Test insufficient inventory
val orderWithTooManyItems = OrderRequest(
customerId = "customer123",
items = listOf(OrderItem("rare-product", 1000, BigDecimal("1.00")))
)
// Test payment failure
val orderWithBadPayment = OrderRequest(
customerId = "customer123",
paymentInfo = PaymentInfo(cardNumber = "invalid")
)
val workflow = workflowClient.newWorkflowStub(OrderWorkflow::class.java)
val result1 = workflow.processOrder(orderWithTooManyItems)
val result2 = workflow.processOrder(orderWithBadPayment)
Error Handling Patterns
Error Classification:
Error Type | Strategy | Compensation | Example |
---|---|---|---|
Validation | Fail fast | None | Invalid email |
Business Rule | Fail fast | None | Insufficient funds |
Resource | Retry + Compensate | Release resources | Database lock |
External Service | Circuit breaker | Full rollback | Payment gateway |
💡 Key Takeaways
What You've Learned:
- ✅ Custom exceptions provide meaningful error context
- ✅ Compensation patterns ensure data consistency
- ✅ Circuit breakers prevent cascading failures
- ✅ Graceful degradation maintains system stability
- ✅ Error classification determines appropriate handling strategy
🚀 Next Steps
You now understand building error-resilient workflows!
Lesson 10 will cover:
- Interactive workflow patterns using Signals
- Real-time workflow state queries
- Event-driven workflow behavior
- Long-running approval workflows
Ready to build interactive workflows? Let's continue! 🎉