Trigger Handler

I want to share my ideas on this subject because it looks like there are plenty of flavors from where to pick up your designs and no all are going in the same direction.

The first question I can think of? Do we really need a Trigger Handler or Dispatcher or whoever you want to call it?
For me, the answer is YES!, we do.

It is not only that we need it but it is probably the first piece of code that should hit your environments. Yes, the first thing you should consider is to have a decent trigger structure that will make your domain layer a happy camper right from the start.

I have seen so many environments going south for not been able to get things right from the very beginning.
And there is nothing to lose if you later decided not to use a trigger handler, can’t see why you shouldn’t…. but you lose nothing if you have one in place.

People argue that “we really don’t need a Trigger Handler at the early stages” because “we are still a small org“, “the org will never need more than a few triggers, it is not going to be very active“, and stuff like that. To be honest, I feel that thinking like that will not be the best approach. Right there you are probably making the first bad decision. There are plenty of books talking about this subject and all of them will conclude that you need this type of architectural designs in your org to have some kind of control over your triggers. As OOP is concerned, to have a trigger handler is a very logical and practical component. The most relevant and important fact is that “when you have more than one trigger of the same type, you cannot predict the order of execution “, yes, you can’t ever predict which of those triggers is going to fire first, scary stuff.

For me, that is without any doubt a good reason to make sure I have something in place for my clients. A trigger handler will help me to control these scenarios and reduce any data inconsistency as much as possible. And there is more to think about, but this is already a pretty serious matter.

A lot of the triggers I have seen out there are not following best practices and common reason. The reality is that developers are pushed to write code at a fast pace with short deadlines and limited time to deliver their solutions. That is also the reason why you can find code that is not written following “best practices” techniques.

To point out one more important issues, triggers run on system content mode! .. so for you to be able to control this you will need to create helper classes anyway. Triggers should contain as less code as possible. Triggers should be like bridges unifying code rather than executing complex code logics. And there are more, not @future methods will be allowed, no asynchronous calls and many other restrictions. Yes, you can build helper classes to avoid those restrictions, that is exactly the point.

If you are an architect or have been reading about salesforce architectural designs, you probably know something about the Domain Layer. This layer intends to ensure the behavior in which Apex Triggers are invoked, among other things. Based on OPP principals a trigger handler will enrich our code making our Separation of Concepts more solid and at the same time allowing us to reuse our code. So it is not crazy to say that a good trigger structure will make overall infrastructure more OOP friendly.

So let’s take an example to illustrate what I am saying here.

Let’s say we have a Custom Object, Quote__c. This object has a relationship of one-to-many to opportunities. So, one opportunity can have many Quote__c records. This custom object contains a custom field, Status__c. In contains more than this field but for the sake of this test lets just focus on this one. You could use the Process Builder for this operation, but again, for the sake of this test lets be nice.
We want to delete all those Quotes records from any Opportunity if the Accept one Quote. It one Quote change the status to “Accepted” all other, not – accepted quotes, need to be sent to a better place, recycle bin in this case.

Trigger:

//Trigger initial lines
trigger deleteQuotes on Opportunities(after update) {
      Set<Id> oppIds = new Set<Id>(); 
           for(Opportunity op: trigger.new)
            {
             oppIds.add(op.id); 
            }

//Now we are going to fetch the Quotes__c records from those Opportunities in oppIds
  List<Quote__c> theQuotes = [SELECT Id FROM Quote__c WHERE Opportunities__c IN: oppIds AND Status__c !='Accepted']; 

  if(!theQuotes.isEmpty() && theQuotes.size()>0)
   {
     try{
         delete theQuotes; 

      }catch(DMLException e){
         System.debug('ERROR:' + e);
      } 
}

This trigger is fine and it should work and we can just go on with our lives.

But what happens if time pass by and we realized that our org has increased in the number of triggers and we have more than one ‘after update’ trigger affecting our Quote__c object. But is not only the Quote__c object that is depending on triggers now, many other objects are also using triggers for different business processes with DML operations. So we could start thinking about the possible scenario in which data consistency can be negatively affected by this new situation. If we had our trigger handler in place right from the start we shouldn’t be concerned about what to do now because we built things on top of our architectural design and things are flowing as they should.

As much as I want to make a drama of this situation the reality is that is not, at least not yet. In organizations that are growing slowly, you could easily rectify the situation without not harm done. The true is that in many cases the problem grows and managers don’t want to lose time and/or resources in fixing what they thing ‘ain’t broke’, bad manager.

The true is also that decisions like this are not taken seriously until the smelling organic material has hit the fan, then, and only then they realized the important amount of time, resources, and the most painful one, money they need to allocate to fix something that could have been easily avoided by building a trigger handler.

So, how can we do things a bit better to avoid this enormous irritation? Well, you can build your Trigger Handler or look around for something that you can use, inventing the wheel is a waste in some cases.
I have used Kevin O’Hara’s trigger handler that works perfectly fine, …he even supplies it with a test class!! I really, really recommend you to at least take a look at his work on this subject and give it a try. Learn the structure and use it to your advantage. I have made some small modifications but the overall structure remains the same.

I’m going to explain every single section of the code, you can check Kevin’s article on that, here is the code:

public virtual class TriggerHandler {

  // static map of handlername, times run() was invoked
  private static Map<String, LoopCount> loopCountMap;
  private static Set<String> bypassedHandlers;

  // the current context of the trigger, overridable in tests
  @TestVisible
  private TriggerContext context;

  // the current context of the trigger, overridable in tests
  @TestVisible
  private Boolean isTriggerExecuting;

  // static initialization
  static {
    loopCountMap = new Map<String, LoopCount>();
    bypassedHandlers = new Set<String>();
  }
  
  // constructor
  public TriggerHandler() {
    this.setTriggerContext();
  }

  /***************************************
   * public instance methods
   ***************************************/

  // main method that will be called during execution
  public void run() {

    if(!validateRun()) return;

    addToLoopCount();

    // dispatch to the correct handler method
    if(this.context == TriggerContext.BEFORE_INSERT) {
      this.beforeInsert();
    } else if(this.context == TriggerContext.BEFORE_UPDATE) {
      this.beforeUpdate();
    } else if(this.context == TriggerContext.BEFORE_DELETE) {
      this.beforeDelete();
    } else if(this.context == TriggerContext.AFTER_INSERT) {
      this.afterInsert();
    } else if(this.context == TriggerContext.AFTER_UPDATE) {
      this.afterUpdate();
    } else if(this.context == TriggerContext.AFTER_DELETE) {
      this.afterDelete();
    } else if(this.context == TriggerContext.AFTER_UNDELETE) {
      this.afterUndelete();
    }

  }

  public void setMaxLoopCount(Integer max) {
    String handlerName = getHandlerName();
    if(!TriggerHandler.loopCountMap.containsKey(handlerName)) {
      TriggerHandler.loopCountMap.put(handlerName, new LoopCount(max));
    } else {
      TriggerHandler.loopCountMap.get(handlerName).setMax(max);
    }
  }

  public void clearMaxLoopCount() {
    this.setMaxLoopCount(-1);
  }

  /***************************************
   * public static methods
   ***************************************/

  public static void bypass(String handlerName) {
    TriggerHandler.bypassedHandlers.add(handlerName);
  }

  public static void clearBypass(String handlerName) {
    TriggerHandler.bypassedHandlers.remove(handlerName);
  }

  public static Boolean isBypassed(String handlerName) {
    return TriggerHandler.bypassedHandlers.contains(handlerName);
  }

  public static void clearAllBypasses() {
    TriggerHandler.bypassedHandlers.clear();
  }

  /***************************************
   * private instancemethods
   ***************************************/

  @TestVisible
  private void setTriggerContext() {
    this.setTriggerContext(null, false);
  }

  @TestVisible
  private void setTriggerContext(String ctx, Boolean testMode) {
    if(!Trigger.isExecuting && !testMode) {
      this.isTriggerExecuting = false;
      return;
    } else {
      this.isTriggerExecuting = true;
    }
    
    if((Trigger.isExecuting && Trigger.isBefore && Trigger.isInsert) ||
        (ctx != null && ctx == 'before insert')) {
      this.context = TriggerContext.BEFORE_INSERT;
    } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isUpdate) ||
        (ctx != null && ctx == 'before update')){
      this.context = TriggerContext.BEFORE_UPDATE;
    } else if((Trigger.isExecuting && Trigger.isBefore && Trigger.isDelete) ||
        (ctx != null && ctx == 'before delete')) {
      this.context = TriggerContext.BEFORE_DELETE;
    } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isInsert) ||
        (ctx != null && ctx == 'after insert')) {
      this.context = TriggerContext.AFTER_INSERT;
    } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUpdate) ||
        (ctx != null && ctx == 'after update')) {
      this.context = TriggerContext.AFTER_UPDATE;
    } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isDelete) ||
        (ctx != null && ctx == 'after delete')) {
      this.context = TriggerContext.AFTER_DELETE;
    } else if((Trigger.isExecuting && Trigger.isAfter && Trigger.isUndelete) ||
        (ctx != null && ctx == 'after undelete')) {
      this.context = TriggerContext.AFTER_UNDELETE;
    }
  }

  // increment the loop count
  @TestVisible
  private void addToLoopCount() {
    String handlerName = getHandlerName();
    if(TriggerHandler.loopCountMap.containsKey(handlerName)) {
      Boolean exceeded = TriggerHandler.loopCountMap.get(handlerName).increment();
      if(exceeded) {
        Integer max = TriggerHandler.loopCountMap.get(handlerName).max;
        throw new TriggerHandlerException('Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handlerName);
      }
    }
  }

  // make sure this trigger should continue to run
  @TestVisible
  private Boolean validateRun() {
    if(!this.isTriggerExecuting || this.context == null) {
      throw new TriggerHandlerException('Trigger handler called outside of Trigger execution');
    }
    if(TriggerHandler.bypassedHandlers.contains(getHandlerName())) {
      return false;
    }
    return true;
  }

  @TestVisible
  private String getHandlerName() {
    return String.valueOf(this).substring(0,String.valueOf(this).indexOf(':'));
  }

  /***************************************
   * context methods
   ***************************************/

  // context-specific methods for override
  @TestVisible
  protected virtual void beforeInsert(){}
  @TestVisible
  protected virtual void beforeUpdate(){}
  @TestVisible
  protected virtual void beforeDelete(){}
  @TestVisible
  protected virtual void afterInsert(){}
  @TestVisible
  protected virtual void afterUpdate(){}
  @TestVisible
  protected virtual void afterDelete(){}
  @TestVisible
  protected virtual void afterUndelete(){}

  /***************************************
   * inner classes
   ***************************************/

  // inner class for managing the loop count per handler
  @TestVisible
  private class LoopCount {
    private Integer max;
    private Integer count;

    public LoopCount() {
      this.max = 5;
      this.count = 0;
    }

    public LoopCount(Integer max) {
      this.max = max;
      this.count = 0;
    }

    public Boolean increment() {
      this.count++;
      return this.exceeded();
    }

    public Boolean exceeded() {
      if(this.max < 0) return false;
      if(this.count > this.max) {
        return true;
      }
      return false;
    }

    public Integer getMax() {
      return this.max;
    }

    public Integer getCount() {
      return this.count;
    }

    public void setMax(Integer max) {
      this.max = max;
    }
  }

  // possible trigger contexts
  @TestVisible
  private enum TriggerContext {
    BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE,
    AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE,
    AFTER_UNDELETE
  }

  // exception class
  public class TriggerHandlerException extends Exception {}

}

You can get all details about this virtual class in Kevin’s article but put attention to the run() method.
You can grab the test class here: Trigger Handler Test Class.