Framework For Apex Triggers

Apex Trigger Frameworks are scripts in Apex that execute either before or after data manipulation events, such as record insertion, updating, or deletion. They are crafted to perform tasks beyond the capabilities of point-and-click tools in Salesforce.

Ensuring a well-structured and designed approach to writing triggers adds a layer of sensibility and helps prevent potential roadblocks or major issues in the future.

Various frameworks are employed when implementing triggers on any object, and one widely adopted approach is the Apex Trigger Handler Framework. This framework is renowned for its popularity and widespread usage.

It’s important to note that while all frameworks have their merits, the choice should be made based on the specific needs of the application.

Framework for Handling Salesforce Triggers


Always remember to have only one Trigger per Object before considering writing any Trigger. Having more than one Trigger on an object can lead to uncertainty about the execution order. A straightforward Trigger Handler Framework can effectively manage the order of execution, addressing this concern.

Another challenge arises when attempting to deactivate a trigger in a production environment, as it becomes impossible to achieve. To tackle this issue, incorporating a switch in the trigger becomes essential.

Here are some essential components of an Apex trigger framework:

  • Trigger
  • Trigger Switch
  • Trigger Handler
  • Trigger Helper

For instance, let’s focus on the Account object.

Before diving into Trigger creation, it’s crucial to first configure the Trigger Switch:

  • Create a Custom Metadata: Establish a custom metadata, named Trigger_Configuration__c, to store the configuration for enabling or disabling triggers. Include a checkbox field, such as IsTriggerEnabled__c, to control the trigger’s execution.
  • Configure the Trigger Configuration: Set the value of IsTriggerEnabled__c to true or false in Salesforce to enable or disable the trigger. Utilize the Name field to store the trigger’s name.
  • Modify Your Trigger to Check the Configuration: Within your trigger, verify the value of the IsTriggerEnabled__c field in the Trigger_Configuration__c custom metadata. If the field is set to true, the trigger logic will execute; otherwise, it will be skipped.

With the switch configured, let’s proceed to create the sole Trigger for the Account Object.

// Trigger: AccountTrigger.trigger
 
trigger AccountTrigger on Account ( before insert, 
                                    before update, 
                                    after insert, 
                                    after update, 
                                    before delete, 
                                    after delete) {
    // Query the Trigger_Configuration__c to check if the trigger should be enabled
    Trigger_Configuration__c triggerConfig = Trigger_Configuration__c.getInstance('AccountTriggerConfig'); // Assuming you've created a specific record for this trigger
     
    if (triggerConfig != null && triggerConfig.IsTriggerEnabled__c) {
        // The trigger is enabled; proceed with the trigger logic
        if (Trigger.isBefore) {
            if (Trigger.isInsert) {
                AccountTriggerHandler.handleBeforeInsert(Trigger.new);
            }
            if (Trigger.isUpdate) {
                AccountTriggerHandler.handleBeforeUpdate(   Trigger.new, 
                                                            Trigger.oldMap);
            }
            if (Trigger.isDelete) {
                AccountTriggerHandler.handleBeforeDelete(Trigger.old);
            }
        } else {
            if (Trigger.isInsert) {
                AccountTriggerHandler.handleAfterInsert(Trigger.new);
            }
            if (Trigger.isUpdate) {
                AccountTriggerHandler.handleAfterUpdate(Trigger.new, 
                                                        Trigger.old, 
                                                        Trigger.newMap, 
                                                        Trigger.oldMap);
            }
            if (Trigger.isDelete) {
                AccountTriggerHandler.handleAfterDelete(Trigger.old, 
                                                        Trigger.oldMap);
            }
        }
    }
    // If triggerConfig is null or IsTriggerEnabled__c is false, the trigger will be skipped
}

However, saving the Trigger won’t be possible as it has a reference to the Trigger Handler. Therefore, let’s proceed to create the Trigger Handler for the Account.

// Trigger Handler Class: AccountTriggerHandler.cls
 
public class AccountTriggerHandler {
    public static void handleBeforeInsert(List<Account> newRecords) {
        //Here I am trying to explain how you can reuse one for loop and avoid executing unnecessary lines of code
        List<Account> salesLogicList = new List<Account> ();
        List<Account> serviceLogicList = new List<Account> ();
 
        for(Account acc : newRecords){
            if(acc.recordTypeId == 'sales'){
                salesLogicList.add(acc);
            }else if(acc.recordTypeId == 'service'){
                serviceLogicList.add(acc);
            }
        }
        // Before Insert Logic
        If(!salesLogicList.isEmpty()){
            AccountTriggerHelper.executeSalesLogic(salesLogicList);
        }
        If(!serviceLogicList.isEmpty()){
            AccountTriggerHelper.executeServiceLogic(serviceLogicList);
        }
    }
 
    public static void handleBeforeUpdate(List<Account> newRecords, Map<Id, Account> oldMap) {
        // Before Update Logic
        // Example: Validate changes or perform calculations
        AccountTriggerHelper.validateChanges(newRecords, oldMap);
    }
 
    public static void handleBeforeDelete(List<Account> oldRecords) {
        // Before Delete Logic
        // Example: Check dependencies or perform cleanup
        AccountTriggerHelper.checkDependencies(oldRecords);
    }
 
    public static void handleAfterInsert(List<Account> newRecords) {
        // After Insert Logic
        // Example: Trigger post-insert actions
        AccountTriggerHelper.triggerPostInsertActions(newRecords);
    }
 
    public static void handleAfterUpdate(List<Account> newRecords, List<Account> oldRecords, Map<Id, Account> newMap, Map<Id, Account> oldMap) {
        // After Update Logic
        // Example: Send notifications or log changes
        AccountTriggerHelper.sendNotifications(newRecords, oldRecords);
    }
 
    public static void handleAfterDelete(List<Account> oldRecords, Map<Id, Account> oldMap) {
        // After Delete Logic
        // Example: Archive data or trigger related processes
        AccountTriggerHelper.archiveData(oldRecords);
    }
}

To save the Handler, you must first create the Helper. Here is an example of a Helper class.

// Helper Class: AccountTriggerHelper.cls
 
public class AccountTriggerHelper {
    public static void executeSalesLogic(List<Account> records) {
        // Set default Status__c value
        for (Account record : records) {
            if (record.Status__c == null) {
                record.Status__c = 'New';
            }
        }
    }
 
    public static void executeServiceLogic(List<Account> records) {
        // Set default Status__c value
        for (Account record : records) {
            if (record.Status__c == null) {
                record.Status__c = 'Green';
            }
        }
    }
 
    public static void validateChanges(List<Account> newRecords, Map<Id, Account> oldMap) {
        // Validation logic for changes
        // Example: Check if certain fields were modified
    }
 
    public static void checkDependencies(List<Account> records) {
        // Dependency checks before deletion
    }
 
    public static void triggerPostInsertActions(List<Account> records) {
        // Additional actions after insert
    }
 
    public static void sendNotifications(List<Account> newRecords, List<Account> oldRecords) {
        // Send notifications or log changes
    }
 
    public static void archiveData(List<Account> records) {
        // Archive data or trigger related processes after deletion
    }
}

Note: In this attempt, I aimed to simplify and enhance the effectiveness of the framework to serve its purpose. However, there are various ways to write and enhance this framework based on specific needs.