-
Notifications
You must be signed in to change notification settings - Fork 0
Best Practices
This guide outlines best practices for building robust, efficient async action processors. Following these patterns will help you create processors that are maintainable, performant, and resilient.
Every action must end the transaction with either a completed status or be handled by the failure mechanism. Actions left without status updates will remain in 'Pending' status indefinitely.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
// ❌ Bad - Action status never updated
for (AsyncAction__c action : actions) {
processBusinessLogic(action);
// Missing status update - action stays pending forever!
}
// ✅ Good - Every action gets a final status
for (AsyncAction__c action : actions) {
try {
processBusinessLogic(action);
action.Status__c = AsyncActions.Status.COMPLETED.name();
} catch (Exception error) {
new AsyncActions.Failure(settings).fail(action, error);
}
}
}Avoid modifying the original actions List parameter, as this may interfere with the framework's error detection. Instead, work with a copy or Map.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
// ❌ Bad - Modifying the original list
actions.remove(0);
actions.add(someNewAction);
// ✅ Good - Work with a Map for processing
Map<Id, AsyncAction__c> actionMap = new Map<Id, AsyncAction__c>(actions);
for (AsyncAction__c action : actionMap.values()) {
// If an action isn't relevant, mark as failed and remove from map:
if (action?.RelatedRecordId__c == null) {
actionMap?.remove(action?.Id);
new AsyncActions.Failure(settings, AsyncActions.RetryBehavior.SUDDEN_DEATH)
.fail(action, 'Missing RelatedRecordId__c');
}
}
// Now process any remaining valid actions:
this.processValidActions(actionMap);
}Validate input data early and fail gracefully for invalid data.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
List<AsyncAction__c> validActions = new List<AsyncAction__c>();
List<AsyncAction__c> invalidActions = new List<AsyncAction__c>();
for (AsyncAction__c action : actions) {
if (action.RelatedRecordId__c == null) {
invalidActions.add(action);
} else {
validActions.add(action);
}
}
// Fail invalid actions immediately with SUDDEN_DEATH
if (!invalidActions.isEmpty()) {
new AsyncActions.Failure(settings, AsyncActions.RetryBehavior.SUDDEN_DEATH)
.fail(invalidActions, 'Missing required RelatedRecordId');
}
// Process valid actions
if (!validActions.isEmpty()) {
processValidActions(validActions);
}
}When using the Data__c field, validate and parse JSON data safely.
private Map<String, Object> parseActionData(AsyncAction__c action) {
if (String.isBlank(action.Data__c)) {
return new Map<String, Object>();
}
try {
return (Map<String, Object>) JSON.deserializeUntyped(action.Data__c);
} catch (Exception error) {
// Handle invalid JSON gracefully
return new Map<String, Object>();
}
}The framework automatically handles Async Action record updates at the end of each batch. For this reason, you do not need to perform DML on these records.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
// ❌ Bad - Manual DML on AsyncAction records
update actions;
// ✅ Good - Only DML on business records
List<Task> tasksToInsert = this.generateTasksFromActions(actions);
insert tasksToInsert;
}Use the framework's built-in failure handling for consistent retry behavior.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
try {
// Your business logic here
processActions(actions);
// Mark successful actions as completed
for (AsyncAction__c action : actions) {
action.Status__c = 'Completed';
}
} catch (Exception error) {
// Let the framework handle retries
new AsyncActions.Failure(settings, AsyncActions.RetryBehavior.ALLOW_RETRY)
.fail(actions, error);
}
}Handle scenarios where some actions succeed and others fail within the same batch using allOrNone=false DML operations.
public void process(AsyncActionProcessor__mdt settings, List<AsyncAction__c> actions) {
List<SObject> recordsToInsert = new List<SObject>();
for (AsyncAction__c action : actions) {
SObject record = this.createRecordForAction(action);
recordsToInsert.add(record);
}
List<Database.SaveResult> results = Database.insert(recordsToInsert, false);
for (Integer i = 0; i < results.size(); i++) {
AsyncAction__c action = actions[i];
Database.SaveResult result = results[i];
if (result?.isSuccess() == true) {
action.Status__c = 'Completed';
} else {
List<Database.Error> errors = result.getErrors();
new AsyncActions.Failure(settings).fail(action, errors);
}
}
}