Error Handling

What is Error Handling? #

Error handling in Orchesty defines how your integrations respond to failures, from temporary network issues to permanent validation errors. The SDK provides structured ways to handle errors through result codes, exception types, and process control, ensuring failures are logged, retried when appropriate, and routed correctly.

Key concepts:

  • Result codes: Indicate success, failure, or retry status
  • Exceptions: Special error types that control flow (retry, stop)
  • ProcessDto status: Mark messages as successful or failed
  • Trash: Failed messages that need manual intervention
  • Error propagation: How errors move through topologies

Why Error Handling Matters #

Reliability #

  • Graceful degradation: Systems don't crash on errors
  • Automatic recovery: Transient errors retry automatically
  • Data integrity: Failed messages don't get lost
  • Clear failures: Permanent errors stop processing cleanly

Debugging #

  • Error context: Know exactly what failed and why
  • Stack traces: See where errors originated
  • Correlation tracking: Trace errors through multiple nodes
  • Error patterns: Identify recurring issues

Business Logic #

  • Validation: Stop processing invalid data
  • Conditional flow: Route based on success/failure
  • Partial failures: Handle some items failing in batches
  • Alerting: Notify on critical errors

Result Codes #

Result codes indicate the outcome of processing:

SUCCESS #

Processing completed successfully, continue to followers:

import ResultCode from '@orchesty/nodejs-sdk/lib/Utils/ResultCode';

dto.setSuccessProcess('Customer data fetched successfully');
// Or ResultCode.SUCCESS (default)
return dto;

STOP_AND_FAILED #

Processing failed permanently, do not retry:

// Validation error - don't retry
if (!data.email) {
    dto.setStopProcess(
        ResultCode.STOP_AND_FAILED,
        'Email is required'
    );
    return dto;
}

DO_NOT_CONTINUE #

Stop processing but don't mark as failed (filter out):

// Filter out low-value orders
if (order.total < 10) {
    dto.setStopProcess(
        ResultCode.DO_NOT_CONTINUE,
        'Order value below threshold'
    );
    return dto;
}

Result Code Flow #

graph TB
    Start[Process Message]
    Process{Execute Node}
    Success[SUCCESS]
    Failed[STOP_AND_FAILED]
    Filter[DO_NOT_CONTINUE]
    Retry[REPEAT]
    Followers[Continue to Followers]
    Trash[Send to Trash]
    Discard[Discard Message]
    RetryQueue[Retry Queue]
    
    Start --> Process
    Process -->|Success| Success
    Process -->|Validation Error| Failed
    Process -->|Filtered Out| Filter
    Process -->|Network Error| Retry
    
    Success --> Followers
    Failed --> Trash
    Filter --> Discard
    Retry --> RetryQueue
    RetryQueue --> Process

Exception Types #

OnRepeatException #

Triggers automatic retry mechanism:

import OnRepeatException from '@orchesty/nodejs-sdk/lib/Exception/OnRepeatException';

try {
    const result = await this.callAPI();
    dto.setJsonData(result);
} catch (error) {
    if (error.code === 'ETIMEDOUT') {
        // Retry every 30 seconds, up to 5 times
        throw new OnRepeatException(
            30,    // interval (seconds)
            5,     // max retries
            `Network timeout: ${error.message}`
        );
    }
    throw error;
}

When to use:

  • Network timeouts
  • Temporary API unavailability
  • Rate limiting (429 errors)
  • Resource locks
  • Async operations not complete

OnStopAndFailException #

Stops processing with failure status:

import OnStopAndFailException from '@orchesty/nodejs-sdk/lib/Exception/OnStopAndFailException';

if (!this.isValid(data)) {
    throw new OnStopAndFailException(
        'Invalid data format',
        dto
    );
}

When to use:

  • Validation failures
  • Missing required fields
  • Authentication errors
  • Permanent API errors (404, 403)
  • Business logic violations

Standard JavaScript Errors #

Regular errors stop processing and go to trash:

try {
    const result = await this.process();
    dto.setJsonData(result);
} catch (error) {
    // Standard error - goes to trash
    throw new Error(`Processing failed: ${error.message}`);
}

Error Handling Patterns #

Pattern 1: Validation Errors #

import AConnector from '@orchesty/nodejs-sdk/lib/Connector/AConnector';
import ProcessDto from '@orchesty/nodejs-sdk/lib/Utils/ProcessDto';
import ResultCode from '@orchesty/nodejs-sdk/lib/Utils/ResultCode';

export default class ValidateCustomerConnector extends AConnector {
    
    public getName(): string {
        return 'validate-customer';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        const customer = dto.getJsonData();
        
        // Validate required fields
        const errors: string[] = [];
        
        if (!customer.email) {
            errors.push('Email is required');
        } else if (!this.isValidEmail(customer.email)) {
            errors.push('Email format is invalid');
        }
        
        if (!customer.name) {
            errors.push('Name is required');
        }
        
        if (!customer.phone) {
            errors.push('Phone is required');
        }
        
        // Stop if validation fails
        if (errors.length > 0) {
            dto.setStopProcess(
                ResultCode.STOP_AND_FAILED,
                `Validation failed: ${errors.join(', ')}`
            );
            return dto;
        }
        
        // Validation passed
        dto.setSuccessProcess('Customer data is valid');
        return dto;
    }
    
    private isValidEmail(email: string): boolean {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
}

Pattern 2: API Error Handling #

import OnRepeatException from '@orchesty/nodejs-sdk/lib/Exception/OnRepeatException';
import ResultCode from '@orchesty/nodejs-sdk/lib/Utils/ResultCode';

export default class ApiCallConnector extends AConnector {
    
    public getName(): string {
        return 'api-call';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        try {
            const requestDto = new RequestDto(
                'https://api.example.com/data',
                HttpMethods.GET,
                dto
            );
            
            const response = await this.getSender().send(requestDto);
            dto.setData(response.getBody());
            
            return dto;
            
        } catch (error) {
            const status = error.response?.status;
            
            // Handle different error types
            if (status === 429) {
                // Rate limited - retry with longer interval
                throw new OnRepeatException(
                    300,  // 5 minutes
                    3,
                    'API rate limit exceeded'
                );
            }
            
            if (status === 404) {
                // Not found - permanent error
                dto.setStopProcess(
                    ResultCode.STOP_AND_FAILED,
                    `Resource not found: ${error.message}`
                );
                return dto;
            }
            
            if (status === 401 || status === 403) {
                // Authentication error - permanent
                dto.setStopProcess(
                    ResultCode.STOP_AND_FAILED,
                    'Authentication failed. Please check credentials.'
                );
                return dto;
            }
            
            if (status >= 500) {
                // Server error - retry
                throw new OnRepeatException(
                    30,
                    10,
                    `Server error: ${error.message}`
                );
            }
            
            if (error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET') {
                // Network error - retry
                throw new OnRepeatException(
                    20,
                    5,
                    `Network error: ${error.message}`
                );
            }
            
            // Unknown error - fail permanently
            dto.setStopProcess(
                ResultCode.STOP_AND_FAILED,
                `API call failed: ${error.message}`
            );
            return dto;
        }
    }
}

Pattern 3: Try-Catch with Logging #

import logger from '@orchesty/nodejs-sdk/lib/Logger/Logger';

export default class SafeProcessingConnector extends AConnector {
    
    public getName(): string {
        return 'safe-processing';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        try {
            logger.info('Starting processing', dto);
            
            const input = dto.getJsonData();
            const result = await this.complexOperation(input);
            
            dto.setJsonData(result);
            logger.info('Processing completed successfully', dto);
            
            return dto;
            
        } catch (error) {
            // Log the error with full context
            logger.error(
                'Processing failed',
                dto,
                error instanceof Error ? error : new Error(String(error))
            );
            
            // Decide how to handle
            if (this.isRetryable(error)) {
                throw new OnRepeatException(
                    30,
                    5,
                    `Retryable error: ${error.message}`
                );
            } else {
                dto.setStopProcess(
                    ResultCode.STOP_AND_FAILED,
                    `Fatal error: ${error.message}`
                );
                return dto;
            }
        }
    }
    
    private isRetryable(error: any): boolean {
        const retryableCodes = ['ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND'];
        return retryableCodes.includes(error.code) || error.status >= 500;
    }
}

Pattern 4: Partial Batch Failures #

import ABatchNode from '@orchesty/nodejs-sdk/lib/Batch/ABatchNode';
import BatchProcessDto from '@orchesty/nodejs-sdk/lib/Utils/BatchProcessDto';
import logger from '@orchesty/nodejs-sdk/lib/Logger/Logger';

export default class BatchWithErrorsNode extends ABatchNode {
    
    public getName(): string {
        return 'batch-with-errors';
    }
    
    public async processAction(dto: BatchProcessDto): Promise<BatchProcessDto> {
        const items = dto.getJsonData().items;
        
        let successCount = 0;
        let failureCount = 0;
        
        for (const item of items) {
            try {
                // Process item
                const result = await this.processItem(item);
                
                // Add successful items
                dto.addItem(result);
                successCount++;
                
            } catch (error) {
                failureCount++;
                
                // Log failure but continue processing other items
                logger.warning(
                    `Failed to process item ${item.id}: ${error.message}`,
                    dto
                );
                
                // Optionally add to failed items list
                // dto.addItem({ ...item, error: error.message, failed: true });
            }
        }
        
        logger.info(
            `Batch complete: ${successCount} succeeded, ${failureCount} failed`,
            dto
        );
        
        return dto;
    }
}

Pattern 5: Graceful Degradation #

export default class FallbackConnector extends AConnector {
    
    public getName(): string {
        return 'fallback';
    }
    
    public async processAction(dto: ProcessDto): Promise<ProcessDto> {
        try {
            // Try primary API
            const result = await this.callPrimaryAPI(dto);
            dto.setJsonData(result);
            
        } catch (primaryError) {
            logger.warning(
                'Primary API failed, trying fallback',
                dto,
                primaryError
            );
            
            try {
                // Try fallback API
                const result = await this.callFallbackAPI(dto);
                dto.setJsonData({
                    ...result,
                    source: 'fallback',
                    primaryError: primaryError.message
                });
                
            } catch (fallbackError) {
                // Both failed - use cached data
                logger.error(
                    'Both APIs failed, using cached data',
                    dto,
                    fallbackError
                );
                
                const cached = await this.getCachedData();
                if (cached) {
                    dto.setJsonData({
                        ...cached,
                        source: 'cache',
                        warning: 'Using cached data due to API failures'
                    });
                } else {
                    // No fallback available - fail
                    dto.setStopProcess(
                        ResultCode.STOP_AND_FAILED,
                        'All data sources unavailable'
                    );
                }
            }
        }
        
        return dto;
    }
}

HTTP Error Handling #

Default HTTP Error Behavior #

The SDK automatically handles HTTP errors via CurlSender:

// Default behavior:
// - 2xx: Success
// - 4xx (except 408): Stop and fail
// - 408: Retry
// - 5xx: Retry
// - Network timeouts: Retry

Custom HTTP Error Handling #

import { IResultRanges } from '@orchesty/nodejs-sdk/lib/Transport/Curl/ResultCodeRange';

public async processAction(dto: ProcessDto): Promise<ProcessDto> {
    const requestDto = new RequestDto(url, HttpMethods.GET, dto);
    
    // Custom error handling
    const codeRanges: IResultRanges = {
        success: '<300',                              // 2xx = success
        stopAndFail: ['300-408', '409-429', '430-499'],  // Most 4xx = fail
        repeat: [408, 429, '>=500']                   // Timeout, rate limit, 5xx = retry
    };
    
    const response = await this.getSender().send(
        requestDto,
        codeRanges,
        30,   // retry interval
        5     // max retries
    );
    
    dto.setData(response.getBody());
    return dto;
}

The Trash #

What is the Trash? #

Messages that fail permanently go to the "trash" in Orchesty Admin. From there, you can:

  • View the error message
  • See the full message data
  • Edit and retry the message
  • Delete the message

Messages Go to Trash When: #

  1. Result code is STOP_AND_FAILED
dto.setStopProcess(ResultCode.STOP_AND_FAILED, 'Error message');
  1. Unhandled exceptions thrown
throw new Error('Something went wrong');
  1. Max retry attempts exceeded
// After 5 retry attempts fail
throw new OnRepeatException(30, 5, 'Still failing');

Preventing Trash #

// Use DO_NOT_CONTINUE for filtered messages (not errors)
if (shouldSkip) {
    dto.setStopProcess(
        ResultCode.DO_NOT_CONTINUE,
        'Message filtered out'
    );
}

// Handle errors gracefully
try {
    await process();
} catch (error) {
    // Return fallback data instead of failing
    dto.setJsonData(fallbackData);
    dto.setSuccessProcess('Used fallback data');
}

Error Messages #

Writing Good Error Messages #

// Good - specific and actionable
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    'Customer email "invalid-email" does not match format: user@domain.com'
);

// Bad - vague
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    'Invalid data'
);

Include Context #

// Good - includes IDs and context
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    `Failed to create order for customer ${customerId}: Product ${productId} is out of stock`
);

// Bad - no context
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    'Product unavailable'
);

Suggest Solutions #

// Good - suggests fix
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    'API key is invalid or expired. Please update credentials in Orchesty Admin.'
);

// Bad - just states problem
dto.setStopProcess(
    ResultCode.STOP_AND_FAILED,
    'Authentication failed'
);

Best Practices #

1. Fail Fast for Validation #

// Good - validate early
public async processAction(dto: ProcessDto): Promise<ProcessDto> {
    const data = dto.getJsonData();
    
    // Validate immediately
    if (!data.email) {
        dto.setStopProcess(ResultCode.STOP_AND_FAILED, 'Email required');
        return dto;
    }
    
    // Continue processing...
}

// Bad - validate after expensive operations
public async processAction(dto: ProcessDto): Promise<ProcessDto> {
    const result = await this.expensiveOperation();  // Wasted if invalid
    
    if (!result.email) {
        dto.setStopProcess(ResultCode.STOP_AND_FAILED, 'Email required');
        return dto;
    }
}

2. Distinguish Temporary from Permanent Errors #

try {
    await this.operation();
} catch (error) {
    // Temporary - retry
    if (error.code === 'ETIMEDOUT') {
        throw new OnRepeatException(30, 5, error.message);
    }
    
    // Permanent - fail
    if (error.status === 404) {
        dto.setStopProcess(ResultCode.STOP_AND_FAILED, 'Not found');
        return dto;
    }
}

3. Always Log Errors #

try {
    await this.process();
} catch (error) {
    // Always log before handling
    logger.error('Processing failed', dto, error);
    
    // Then handle
    throw error;
}

4. Provide Error Context #

try {
    const customer = await this.fetchCustomer(customerId);
} catch (error) {
    // Include what you were trying to do
    throw new Error(
        `Failed to fetch customer ${customerId} from API: ${error.message}`
    );
}

5. Clean Up on Errors #

let resource;
try {
    resource = await this.acquireResource();
    await this.process(resource);
} catch (error) {
    logger.error('Processing failed', dto, error);
    throw error;
} finally {
    // Always clean up
    if (resource) {
        await this.releaseResource(resource);
    }
}

6. Don't Swallow Errors #

// Bad - error lost
try {
    await this.process();
} catch (error) {
    // Nothing - error disappears!
}

// Good - log and handle
try {
    await this.process();
} catch (error) {
    logger.error('Process failed', dto, error);
    dto.setStopProcess(ResultCode.STOP_AND_FAILED, error.message);
    return dto;
}

Debugging Failed Messages #

View Error Details #

In Orchesty Admin trash:

  • Error message
  • Stack trace
  • Input data
  • Correlation ID
  • Timestamp

Reproduce Locally #

// Extract data from trash
const failedData = {
    customerId: "123",
    // ... data from trash
};

// Run connector locally
const dto = new ProcessDto();
dto.setJsonData(failedData);

const connector = new MyConnector();
const result = await connector.processAction(dto);

Fix and Retry #

  1. Identify the issue from error message
  2. Fix the code or data
  3. Edit message in trash (if data issue)
  4. Click "Retry" in Orchesty Admin

API References #

Next Steps #

  1. Learn about Retry Policy for automatic error recovery
  2. Explore Logging to debug errors effectively
  3. Understand Data Flow to see how errors affect processing
  4. Read ProcessDto reference for error handling methods
© 2025 Orchesty Solutions. All rights reserved.