Step Handler Best Practices

A comprehensive guide to writing robust, idempotent, and maintainable step handlers in Tasker workflows

Table of Contents

Overview

Step handlers are the core business logic units in Tasker workflows. While the system only requires a process method, following established patterns ensures:

  • Idempotency: Operations can be safely retried without side effects

  • Retry Safety: Clear separation between retryable and permanent failures

  • Maintainability: Consistent structure across your codebase

  • Testability: Predictable interfaces for comprehensive testing

Core Philosophy

The business logic call must be safe to retry, or intentionally not retryable, in a way that ensures idempotency.

Too many distributed systems enter invalid states due to poorly handled secondary actions. By following these patterns, we prevent such issues.

The Four-Phase Pattern

Every step handler should follow this proven four-phase pattern:

Phase 1: Extract and Validate Inputs

  • Extract all required data from task context and previous step results

  • Validate inputs early with PermanentError for missing/invalid data

  • Normalize data formats (e.g., symbolize keys)

  • Fail fast with clear error messages

Phase 2: Execute Business Logic

  • Perform the core operation (computation, API call, data transformation)

  • Handle service-specific errors appropriately

  • Return raw results for phase 3 validation

Phase 3: Validate Business Logic Results

  • Ensure the operation completed successfully

  • Check for business-level failures (e.g., payment declined)

  • Classify errors correctly (PermanentError vs RetryableError)

Phase 4: Process Results (Optional)

  • Override process_results when needed

  • Format and store step results safely

  • Handle result processing errors as PermanentError (don't retry business logic)

Implementation Patterns

Basic Step Handler Structure

Input Extraction and Validation Pattern

API Configuration Pattern

For Tasker::StepHandler::Api handlers, configure the base URL and connection settings in the YAML configuration, not in the handler code:

The framework automatically passes this configuration to the handler:

Business Logic Execution Pattern

Result Validation Pattern

Error Handling & Retry Safety

Error Classification Principles

Use PermanentError for:

  • Missing or invalid input data

  • Authentication/authorization failures

  • Business rule violations (insufficient funds, invalid state)

  • Structural errors (missing required fields in responses)

Use RetryableError for:

  • Network timeouts and connection errors

  • Service unavailable (5xx status codes)

  • Rate limiting (429 status codes)

  • Temporary resource constraints

Error Classification Examples

Idempotency Principles

Key Concepts

  1. Idempotent Operations: Can be performed multiple times with the same result

  2. Side Effect Isolation: Separate side effects from core business logic

  3. State Verification: Check current state before making changes

  4. Compensation Patterns: Handle partial failures gracefully

Idempotency Implementation Patterns

Base Class Selection

Choose the appropriate base class based on your step handler's purpose:

Tasker::StepHandler::Base

  • Use for: Computational tasks, data transformations, internal operations

  • Examples: Data validation, calculations, internal service calls

  • Pattern: Direct business logic execution

Tasker::StepHandler::Api

  • Use for: HTTP API calls, external service integration

  • Examples: Payment processing, user creation, external notifications

  • Pattern: HTTP client with automatic retry and circuit breaker support

  • Configuration: URL and connection settings via YAML handler_config

YAML Configuration:

Testing Patterns

Comprehensive Test Structure

Examples from the Blog Series

Post 01: E-commerce Reliability Pattern

File: spec/blog/fixtures/post_01_ecommerce_reliability/step_handlers/process_payment_handler.rb

  • Phase 1: Validates payment method, token, and cart total

  • Phase 2: Calls MockPaymentService with retry logic

  • Phase 3: Checks payment status and handles declines vs temporary failures

  • Phase 4: Formats payment results with transaction details

Key Learning: Payment declines are permanent errors, but rate limiting is retryable.

Post 02: Data Pipeline Resilience Pattern

File: spec/blog/fixtures/post_02_data_pipeline_resilience/step_handlers/extract_orders_handler.rb

  • Phase 1: Validates date range parameters

  • Phase 2: Extracts data from MockDataWarehouseService

  • Phase 3: Verifies extraction completed successfully

  • Phase 4: Stores extraction metadata and metrics

Key Learning: Data extraction timeouts are retryable, but query errors are permanent.

Post 03: Microservices Coordination Pattern

File: spec/blog/fixtures/post_03_microservices_coordination/step_handlers/create_user_account_handler.rb

  • Phase 1: Validates user data requirements

  • Phase 2: HTTP API call to user service with circuit breaker

  • Phase 3: Handles HTTP status codes and idempotency checks

  • Phase 4: Processes different response scenarios (created vs already exists)

Key Learning: User conflicts require idempotency checks to determine if it's an error or success.

Post 04: Team Scaling Pattern

File: spec/blog/fixtures/post_04_team_scaling/step_handlers/customer_success/execute_refund_workflow_handler.rb

  • Phase 1: Validates approval workflow and maps team-specific data

  • Phase 2: Cross-namespace HTTP API call to payments team

  • Phase 3: Handles task creation status from remote team

  • Phase 4: Formats delegation results with correlation tracking

Key Learning: Cross-team coordination requires careful data mapping and correlation tracking.

Common Anti-Patterns to Avoid

❌ Don't Do This

✅ Do This Instead

Summary

Following these patterns ensures:

  1. Robust Error Handling: Clear classification of permanent vs retryable errors

  2. Idempotent Operations: Safe retry behavior without side effects

  3. Maintainable Code: Consistent structure across all step handlers

  4. Comprehensive Testing: Predictable interfaces for thorough test coverage

  5. Production Readiness: Battle-tested patterns from real-world usage

Remember: The goal is not rigid adherence to rules, but building reliable, maintainable distributed systems that handle failures gracefully.


This guide is maintained as a living document based on patterns proven in the Tasker blog examples and production usage.

Last updated