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: #
- Result code is STOP_AND_FAILED
dto.setStopProcess(ResultCode.STOP_AND_FAILED, 'Error message');
- Unhandled exceptions thrown
throw new Error('Something went wrong');
- 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 #
- Identify the issue from error message
- Fix the code or data
- Edit message in trash (if data issue)
- Click "Retry" in Orchesty Admin
Related Concepts #
- Retry Policy - Automatic retry mechanism
- Logging - Recording errors for debugging
- Data Flow - How errors affect message flow
- Connector - Where errors occur
- Routing - HTTP error responses
API References #
- ResultCode - Result code enum
- OnRepeatException - Retry exception
- OnStopAndFailException - Stop exception
- ProcessDto - Process control methods
- ErrorHandler - Error middleware
Next Steps #
- Learn about Retry Policy for automatic error recovery
- Explore Logging to debug errors effectively
- Understand Data Flow to see how errors affect processing
- Read ProcessDto reference for error handling methods