Trigger Handlers
By: Gavin Palmer
Tags: Apex, Salesforce, Sfdx

Anyone who has developed on Salesforce for any amount of time, will have come across trigger handler frameworks. They come in all shapes and sizes and have I’m sure been written about to death. So I thought it would be a great idea to add to that mass of trigger framework information out there with my own take. I would like to talk a little bit about some of the negatives of common trigger handler patterns (or anti-patterns) I have seen used in the wild, and how you can avoid them.

One of the most common trigger handler patterns I have seen revolves around a single class which then has a number of methods on it for each action (beforeInsert, afterUpdate, etc), the class can then be extended and each method implemented as appropriate with the logic added in. A quick example of this is shown below.

This method has some definite benefits, as it maintains a single point of entry for your triggers. Which allows you to add things like trigger control (ie a switch to disable triggers) or commonly a record counter, although this has its own potential risks/issues which I have detailed below.

The downsides of this approach are twofold, firstly, the logic (the switch statement above) is slightly unnecessary complexity, as each trigger knows its context on the entrance (i.e where you put (before update) in the trigger definition). Secondly, the implementation of trigger handlers often fall into a number of traps, I have detailed these below:

Interweaved Logic Separated Methods & Logic Dispatcher Logic

For each of these, I will explain their usage using an example of a trigger on account with multiple actions/features required on it. Let’s say we require a postcode validation based off some regex patterns stored within a custom metadata type and we need to trigger an integration of the account over to the ERP. For the sake of the example, we will say that both of these are planned to be implemented within the after insert context, regardless of if this is the most appropriate place to put the integration and validation.

Interweaved Logic

This is achieved by attempting to optimise the code by looping over all the records and building up a series of maps or lists, before executing the logic on those lists. I have shown this below:

The problem here is mainly down to the Single Responsibility Principle, which simply put states that any class or function should have only 1 reason to change. This method obviously has 2, if we need to update the validation logic, we have to go in here, if we have to change the integration logic we have to go in here.

Also with the logic being quite tightly intertwined it becomes difficult to know where one starts and ends. What if I wanted to add some logic to create an opportunity for each new account, I may want it to execute after the postcode validation but before the integration. It’s not immediately obvious where that should be added. This problem will only grow with complexity as more and more logic is added.

Separation of Methods & Logic

This solution attempts to resolve some of the issues mentioned above around the intertwined logic, by creating distinct methods for each action that needs to be performed. The obvious downside here is that now we must have multiple loops over the records that enter each trigger, but this can easily be dismissed as pre-optimisation. Let’s have a look at an example of this pattern.

The issue with this approach again comes down to the single responsibility principle, the class itself still has multiple reasons to change. As with the previous method, the complexity of the class can and will grow over time, in fact one of the first customers I worked on had a TriggerHandler type class that was 6000 lines long! This can become a real problem for a number of reasons, firstly it can be very daunting seeing a class file of that size and jumping around all the methods up and down to understand what it is doing. Secondly, with multiple developers working on the same case trigger it can become a massive pain when working with version control (which you likely should be). Finally (and perhaps most importantly), it makes it very difficult to reuse any of the logic that has been implemented in the class.

Dispatcher Logic

This pattern is named so because it effectively implements the handler logic as some form of dispatcher class. That is it takes the mistakes of the previous examples and solves them by passing off the responsibility to other classes.

This largely solves the problems with the other 2 approaches, and in some ways, this option is the better of the 3. The concern is that the handler largely becomes boilerplate, unnecessary code. Often the method calls are implemented as static methods (definitely not the worst problem in the world but where we have an object-oriented language it is worth using the power of it), the concern is often these methods are implemented in a helper type class which falls back into the same problems as above.

Also, with this approach is the nature of the class is no longer a handler, in fact it is really doing some dispatching logic (as the name states), and in a pretty inelegant way at that. We have this series of static classes that get called into all with slightly different forms and implementations, when the contract of what they are doing is largely the same, which hints at some form of OO to be used here instead.

Loop Counters

As mentioned above its common to implement some form of loop counter in the trigger handler, this might be in the form of checking the number of times a specific trigger has executed or even the number of times a record has executed through a trigger. I would argue this approach is more of a band-aid solution to recursion issues. As there may be valid scenarios where you want the same trigger to execute multiple times, even for the same record. For example validation on a record should be executing any time that record has been changed and committed. In 90% of cases recursion issues can be solved through strict criteria of what should be executed on and in that 10% you should implement the loop counter only for the action that is being performed, not for all other triggers.

Summary

There is an argument to say the patterns above are valid in small organisations with low amounts of code, I would argue in those smaller organisations it might actually be better/simpler to implement the logic directly in the trigger. I know this is a bold statement, let me explain, writing the logic directly in the trigger is the easiest to understand and reason about, as you only have to look in one place. If there is only 1 or 2 triggers within an organisation, adding in a complex trigger framework consisting of Only once the complexity is introduced should you really start to introduce more complex patterns and frameworks.

I have spent most of this article pointing holes at common trigger patterns, but I haven’t yet given any constructive options around this issue. I have implemented a trigger framework that takes into account the concerns I have mentioned above. This can be found in GitHub, the trigger framework consists of a few classes, mainly the TriggerDispatcher, which can be called directly within a trigger (ie calling the run() method), passing in a list of Triggerable actions, calling them in order. This then enforces separation of concerns by moving each action into their own class and ensures the full list of records that enter the trigger are looped over once.

The dispatcher class also builds in a number of useful features, for example wrapping the inbound records in a SObjectTriggerWrapper to allow simpler interrogation of the changed fields. Also tracks the trigger stack, writes logs and allows disabling of certain actions and triggers.