Executing Apex Steps
Available: Starter and Premium as a paid add-on
Audience: Developers
Overview
Apex Steps in STEP Orchestrator provide a powerful way to execute custom business logic within your orchestration workflows. Unlike Flow steps, Apex steps offer unlimited flexibility for complex operations, API callouts, data transformations, and advanced business logic while maintaining the limit-safe execution benefits of the STEP Orchestrator. For information on using the Step Designer to configure Apex steps, see Using Step Designer.
Each Apex step runs in its own isolated execution container with fresh Salesforce governor limits, making it ideal for resource-intensive operations that would otherwise hit platform limits.
Key Benefits
- Limit-Safe Execution: Each step runs with fresh governor limits
- Containerized Processing: Steps execute in isolation for reliability
- Flexible Business Logic: Unlimited customization for complex operations
- Stateful Chaining: Pass data between steps seamlessly
- Repeat Capability: Execute steps iteratively until completion criteria are met
- Error Recovery: Built-in retry mechanisms and error handling
How Apex Steps Work
Execution Container
Each Apex step executes in its own execution container, which provides:
- Fresh Governor Limits: DML, SOQL, and CPU limits reset for each step
- Isolated Processing: Steps run independently without affecting each other
- Automatic Cleanup: Resources are automatically managed and cleaned up
- Error Isolation: Failures in one step don't affect others
Step Lifecycle
Apex steps follow a simple lifecycle:
- Queued - Step is scheduled for execution
- Running - Step executes in its own container
- Completed - Step finishes successfully or with errors
- Output Available - Results are available for subsequent steps
Creating Your Apex Step
1. Implement the Interface
All Apex steps must implement the Plinqx.step_Interface.iStep_Interface:
public with sharing class MyCustomStep implements Plinqx.step_Interface.iStep_Interface {
public Map<String, Object> doStep(Object payload) {
// Your business logic here
Map<String, Object> result = new Map<String, Object>();
// Process the payload and return results
return result;
}
}
2. Handle Input Parameters
The payload parameter contains your step's input data:
public Map<String, Object> doStep(Object payload) {
Map<String, Object> result = new Map<String, Object>();
// Normalize the payload to a Map
Map<String, Object> input;
if (payload instanceof Map<String, Object>) {
input = (Map<String, Object>) payload;
} else {
input = (Map<String, Object>) JSON.deserializeUntyped(JSON.serialize(payload));
}
// Extract your input parameters
String accountId = (String) input.get('accountId');
Integer batchSize = (Integer) input.get('batchSize');
// Your business logic here...
return result;
}
3. Return Outputs
Return a Map<String, Object> with your step's outputs:
// Set your outputs
result.put('processedRecords', processedAccounts);
result.put('successCount', successCount);
result.put('errorCount', errorCount);
// For repeat steps, include the isDone flag
result.put('isDone', true); // or false to continue repeating
return result;
Repeat Logic
Apex steps can execute repeatedly until completion criteria are met:
public Map<String, Object> doStep(Object payload) {
Map<String, Object> result = new Map<String, Object>();
Map<String, Object> input = normalizePayload(payload);
// Get current progress
Integer currentCount = (Integer) input.get('processedCount');
Integer targetCount = (Integer) input.get('targetCount');
// Process a batch
List<Account> batch = processNextBatch();
currentCount += batch.size();
// Set outputs
result.put('processedCount', currentCount);
result.put('processedBatch', batch);
// Determine if done
if (currentCount >= targetCount) {
result.put('isDone', true);
} else {
result.put('isDone', false);
}
return result;
}
Benefits of Repeat Logic:
- Progress Tracking: Maintain state across iterations
- Batch Processing: Process large datasets in manageable chunks
- Fresh Limits: Each iteration gets new governor limits
- Automatic Continuation: System handles the repeat loop for you
Error Handling
Graceful Error Handling
Handle errors gracefully to prevent orchestration failures:
public Map<String, Object> doStep(Object payload) {
Map<String, Object> result = new Map<String, Object>();
try {
// Your business logic
List<Account> accounts = processAccounts(payload);
result.put('processedAccounts', accounts);
result.put('isDone', true);
} catch (Exception e) {
// Handle errors gracefully
result.put('error', e.getMessage());
result.put('isDone', true); // Stop on error
}
return result;
}
Error Recovery
The STEP Orchestrator provides built-in error recovery:
- Automatic Retries: Failed steps can be automatically retried
- Manual Recovery: Failed steps can be manually retried through the UI
- Error Logging: Comprehensive error details are captured
- Orchestration Continuation: Other steps continue even if one fails
Best Practices
Step Design
- Single Responsibility: Each step should perform one well-defined operation
- Input Validation: Always validate and normalize your inputs
- Meaningful Outputs: Return structured data that's useful for subsequent steps
- Error Handling: Implement proper error handling and recovery
Performance
- Batch Processing: Process data in batches to stay within limits
- Efficient Queries: Use selective SOQL queries and bulk operations
- Memory Management: Avoid storing large objects in memory
- Use Repeat Logic: For large datasets, use repeat logic to process in chunks
Testing
- Unit Tests: Always include comprehensive unit tests
- Edge Cases: Test with various input scenarios and error conditions
- Integration Tests: Test your step within the orchestration context
Complete Example
Here's a complete example of an Apex step that processes accounts:
public with sharing class AccountProcessingStep implements Plinqx.step_Interface.iStep_Interface {
public Map<String, Object> doStep(Object payload) {
Map<String, Object> result = new Map<String, Object>();
try {
// Normalize input
Map<String, Object> input = normalizePayload(payload);
// Extract parameters
String accountType = (String) input.get('accountType');
Integer batchSize = (Integer) input.get('batchSize');
Integer processedCount = (Integer) input.get('processedCount');
// Query accounts to process
List<Account> accounts = [
SELECT Id, Name, Type, BillingCity
FROM Account
WHERE Type = :accountType
AND Id NOT IN (SELECT AccountId FROM Contact WHERE AccountId != null)
LIMIT :batchSize
];
// Process accounts
for (Account acc : accounts) {
acc.BillingCity = 'Updated by Step Orchestrator';
}
update accounts;
// Update progress
processedCount += accounts.size();
// Set outputs
result.put('processedAccounts', accounts);
result.put('processedCount', processedCount);
result.put('batchSize', batchSize);
// Check if done (example: stop after processing 1000 accounts)
if (processedCount >= 1000) {
result.put('isDone', true);
} else {
result.put('isDone', false);
}
} catch (Exception e) {
result.put('error', e.getMessage());
result.put('isDone', true);
}
return result;
}
private Map<String, Object> normalizePayload(Object payload) {
if (payload instanceof Map<String, Object>) {
return (Map<String, Object>) payload;
} else {
return (Map<String, Object>) JSON.deserializeUntyped(JSON.serialize(payload));
}
}
}
Conclusion
Apex steps in STEP Orchestrator provide a powerful, flexible, and reliable way to execute custom business logic within your orchestration workflows. By implementing the iStep_Interface and following best practices, you can create robust and scalable step implementations that leverage the full power of the Salesforce platform while maintaining the benefits of limit-safe, containerized execution.
The combination of fresh governor limits, isolated execution containers, repeat logic support, and comprehensive error handling makes Apex steps an essential tool for complex business process automation in Salesforce.
For information on creating and managing orchestrations with Apex steps, see Using Step Designer.