Salesforce Apex Trigger Framework

Benefits of Apex Trigger Framework:

  • A single trigger per object gives complete control over the order of execution.
  • Implementing trigger logic in handler class, makes unit testing and maintenance much easier.
  • Implementation of best practices.
  • It enforces trigger to work in a consistent way.
  • It allows to prevent trigger recursion without adding separate logic.
  • It is easier to work on a single trigger for multiple developers and reduce the development lifecycle.
  • It allows to make decisions to active/inactive the trigger from transaction and UI as well.

Each trigger must be implemented in a custom setting that allows the trigger to be active/inactive from UI.
Custom Settings: (Trigger Setting)

Trigger Setting Data: (Create record for each trigger)

Trigger Interface: (ITriggerHandler)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Trigger Handler Interface
*/
public interface ITriggerHandler{
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before insert of the records
@Parameters : List<sObject> newList , Map<Id, sObject> newMap
*/
    void beforeInsert(List<sObject> newList);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after insert of the records
@Parameters : List<sObject> newList
*/
    void afterInsert(List<sObject> newList, Map<Id, sObject> newMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before update of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap
*/
    void beforeUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after update of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap
*/
    void afterUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework before delete of the records
@Parameters : List<sObject> oldList , Map<Id, sObject> oldMap
*/            
    void beforeDelete(List<sObject> oldList , Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after delete of the records
@Parameters : Map<Id, sObject> oldMap
*/
    void afterDelete(List<sObject> oldList , Map<Id, sObject> oldMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework after undelete of the records
@Parameters : List<sObject> newList, Map<Id, sObject> newMap
*/
    void afterUnDelete(List<sObject> newList, Map<Id, sObject> newMap);
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Called by the trigger framework to check the trigger for the object is enabled or disabled
@Parameters :
*/
    Boolean isDisabled();
}

Trigger Dispatcher Class:(TriggerDispatcher)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Trigger Dispatcher.
*/
public class TriggerDispatcher {
    
    /*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : It will invoke the appropriate methods on the handler depending on the trigger context.
@Parameters : ITriggerHandler handler
*/
    public static void run(ITriggerHandler handler, string triggerName){
        
        //Check if the trigger is disabled
        if (handler.IsDisabled()){
            return;
        }
        
        //Get the trigger active information from custom settings by trigger name
        Boolean isActive = TriggerSetting__c.getValues(triggerName).isActive__c;
        
        if(isActive){
            //Check trigger context from trigger operation type
            switch on Trigger.operationType {
                
                when BEFORE_INSERT {
                    //Invoke before insert trigger handler
                    handler.beforeInsert(trigger.new);
                }
                when AFTER_INSERT {
                    //Invoke after insert trigger handler
                    handler.afterInsert(trigger.new, trigger.newMap);
                }
                when BEFORE_UPDATE {
                    //Invoke before update trigger handler
                    handler.beforeUpdate(trigger.new, trigger.newMap, trigger.old, trigger.oldMap);
                }
                when AFTER_UPDATE {
                    //Invoke after update trigger handler
                    handler.afterUpdate(trigger.new, trigger.newMap, trigger.old, trigger.oldMap);
                }
                when BEFORE_DELETE {
                    //Invoke before delete trigger handler
                    handler.beforeDelete(trigger.old, trigger.oldMap);
                }
                when AFTER_DELETE {
                    //Invoke after delete trigger handler
                    handler.afterDelete(trigger.old, trigger.oldMap);
                }
                when AFTER_UNDELETE {
                    //Invoke after undelete trigger handler
                    handler.afterUnDelete(trigger.new, trigger.newMap);
                }
            }
        }
    }
}

Account Object Trigger: (AccountTrigger)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Account Object Trigger.
*/
trigger AccountTrigger on Account(before insert, after insert, before update, after update, before delete, after delete, after unDelete) {
    TriggerDispatcher.run(new AccountTriggerHandler(), 'AccountTrigger');
}

Account Object Trigger Handler: (AccountTriggerHandler)

/*
@Author : Biswajeet Samal
@CreatedDate : 20th Oct 2019
@Description : Account Object Trigger Handler.
*/
public class AccountTriggerHandler implements ITriggerHandler{
    
    //Use this variable to disable this trigger from transaction
    public static Boolean TriggerDisabled = false;
    
    //check if the trigger is disabled from transaction
    public Boolean isDisabled(){
        return TriggerDisabled;
    }
    
    public void beforeInsert(List<sObject> newList) {
        
    }
    
    public void afterInsert(List<sObject> newList , Map<Id, sObject> newMap) {
        
    }
    
    public void beforeUpdate(List<sObject> newList, Map<Id, sObject> newMap, List<sObject> oldList, Map<Id, sObject> oldMap) {
        
    }
    
    public void afterUpdate(List<sObject> newList, Map<Id, sObject> newMap,  List<sObject> oldList, Map<Id, sObject> oldMap) {
        
    }
    
    public void beforeDelete(List<sObject> oldList , Map<Id, sObject> oldMap) {
        
    }
    
    public void afterDelete(List<sObject> oldList , Map<Id, sObject> oldMap) {
        
    }
    
    public void afterUnDelete(List<sObject> newList, Map<Id, sObject> newMap) {
        
    }
}

  • Fabien Braconnier

    Hi!
    Very interesting post, i’m trying to implement it but i really don’t understand how i this easier for test class: If i only want to use a trigger for let say before insert operations, i will have to test every method from the Objecttriggerhandler class otherwise the code coverage will fail. How is this better? am i missing something?

  • binay kumar

    It allows to prevent trigger recursion how??