What is Transaction Finalizers ?
Transaction Finalizers in Salesforce Apex are a feature that provides developers with a mechanism to execute logic after a transaction is completed, regardless of whether it's successful or encounters unhandled exceptions. This is particularly useful in the context of Asynchronous Apex, such as future methods, batch jobs, and queueable Apex.
You can specify a piece of Apex code to run after the asynchronous operation with transaction finalizers. This flexibility allows you to adapt to various scenarios, benefiting resource cleanup, logging, notifications, or other post-processing operations, giving you a sense of control over your code execution.
To use a transaction finalizer, you need to implement the Database. Finalizer interface in your Apex class. The execute method in this interface is invoked after the transaction that the asynchronous code is part of either successfully commits or is rolled back due to an exception
Transaction finalizers allow you to attach a post-action sequence to a Queueable job, enabling you to take appropriate action based on the job execution result.
If a Queueable job encounters an unhandled exception, a transaction finalizer can try to re-enqueue the job up to five times in a row. This limit is specific to a sequence of consecutive failures of the same Queueable job. However, the counter is reset when the job successfully completes without any unhandled exceptions.
You can implement a finalizer using an inner class. Additionally, you can use a single class to implement both the Queueable and Finalizer interfaces.
The Queueable job and the Finalizer run in separate Apex and Database transactions. For example, the Queueable can include DML and the Finalizer can include REST callouts. Using a Finalizer doesn’t count as an extra execution against your daily Async Apex limit. Synchronous governor limits apply for the Finalizer transaction, except in these cases where asynchronous limits apply:
Total heap size
Maximum number of Apex jobs added to the queue with System.enqueueJob
Maximum number of methods with the future annotation allowed per Apex invocation
Here's an Example
public class QueueableLoggingFinalizer implements Finalizer, Queueable {// Queueable implementation
// A queueable job that uses LoggingFinalizer to buffer the log
// and commit upon exit, even if the queueable execution fails
public void execute(QueueableContext ctx) {
String jobId = '' + ctx.getJobId();
System.debug('Begin: executing queueable job: ' + jobId);
try {
// Create an instance of LoggingFinalizer and attach it
// Alternatively, System.attachFinalizer(this) can be used instead of instantiating LoggingFinalizer
LoggingFinalizer f = new LoggingFinalizer();
System.attachFinalizer(f);
// While executing the job, log using LoggingFinalizer.addLog()
// Note that addlog() modifies the Finalizer's state after it is attached
DateTime start = DateTime.now();
f.addLog('About to do some work...', jobId);
while (true) {
// Results in limit error
}
} catch (Exception e) {
System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
} finally {
System.debug('Completed: execution of queueable job: ' + jobId);
}
}
// Finalizer implementation
// Logging finalizer provides a public method addLog(message,source) that allows buffering log lines from the Queueable job.
// When the Queueable job completes, regardless of success or failure, the LoggingFinalizer instance commits this buffered log.
// Custom object LogMessage__c has four custom fields-see addLog() method.
// internal log buffer
private List<ApexLogMessage__c> logRecords = new List<ApexLogMessage__c>();
public void execute(FinalizerContext ctx) {
String parentJobId = ctx.getAsyncApexJobId();
System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
// Update the log records with the parent queueable job id
System.Debug('Updating job id on ' + logRecords.size() + ' log records');
for (ApexLogMessage__c log : logRecords) {
log.Request__c = parentJobId; // or could be ctx.getRequestId()
}
// Commit the buffer
System.Debug('committing log records to database');
Database.insert(logRecords, false);
/* as post action - we can also make a callout to share the details about the tranasctions
For example, the Queueable can include DML and the Finalizer can include REST callouts Or DML to Log records
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://b2b.usa.pod1.com');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json;charset=UTF-8');
req.setBody(jsonString);
HttpResponse response = http.send(req);
*/
if (ctx.getResult() == ParentJobResult.SUCCESS) {
System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
} else {
System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
System.debug('Enqueueing another instance of the queueable...');
}
System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
}
public void addLog(String message, String source) {
// append the log message to the buffer
logRecords.add(new ApexLogMessage__c(
DateTime__c = DateTime.now(),
Message__c = message,
Module_Name__c = 'CustomerOnBoarding',
Method_name__c = 'AccountDueDiligenece',
Application_Name__c = 'Salesforce' ));
}
}
Resources
Execution Governors and Limits
Comments
Post a Comment