Last Post on Triggers & Best Practice I finished up with some objections to the common approach to a trigger framework. These can be summarised as:
- The trigger framework does not hide implementation specifics, it’s dependent on “knowing” the name of each trigger handler.
- The abstraction of the trigger handler code often is more complex and does not support a simple and robust development model i.e. it’s difficult to debug.
- The use of sObjects getters and setters creates a position where code can fail due to hard-coded references (to field names) within strings, it’s a recipe for disaster.
This post is about mitigating some of these!
First off, I’m going to fix the problem of having to name each trigger handler by “emulating” type information. To do this I’m going to use some SOSL and a little trick using the ApexClass object.
ApexClass contains the “source” code for each class implemented within Salesforce, if we query the contents of this object we should be able to find all Trigger Handlers. However we cant use SOQL to do this, SOSL however works, so with a little code tweak out trigger class can now simply call:
The Trigger Factory now queries the ApexClass object based on finding the “implements ITriggerEvents” interface, for every instance returned we check to make sure its not the TriggerFactory itself (as we are using the keyword) and adds this to a map.
The Map key is the interesting part, it’s based on the SobjectType. To get this key we query the instance of the class and “ask” the class itself for what SobjectType the class supports (as per this snippit).
So now we have a Trigger Factory that can load all Trigger Handlers and execute the same functionality based on what sObject type has been “triggered”, a poor mans RTTI!
There’s some additional functionality that needs to be built in to aid debugging – we want to throw an exception if we find two instances of the Trigger Handler that support the same SobjectType.
Next issue is around the complexity of the overall solution. Implicitly there’s only so many actions that you want to support in a trigger, although the complexity of these gets complex!
What I’d like is a way of implementing some common routines to answer, “has this field changed?” “what was the old value?” these are questions that are usual within a trigger and cant be applied using the interface model. In addition, I want to do this in a manner that is type save so that I don’t have to use generic sObject. To do this we need to change our approach a little. First I need to discard the use of an Interface and instead use an abstract class.
Using this approach, I’m still enforcing the use of the “supportsType” so my map can be maintained. I’ve also removed all of the other before blah after blah blah and just have a single. Call “execute” this is currently empty but it wont be for long!)
This means the factory class now can look significantly simpler, I’ve also made sure we can only ever have one instance of a trigger handler, after all this is about enforcing best practice.
Okay, now the fun begins …… The execute method!
My approach to this is that I want to remove all of the complex coding from the dev team, that means I need to give them the ability to manipulate collections of sObjects in response to an update, insert or delete. So lets actually make it easy by hiding this complexity. Firstly I’ve created some wrapper calls to hide the trigger context variables but still allow for us to use IsInsert() etc.
Then I created some virtual calls that my Trigger Handler can override if needed, first based on the entire DataSet (Before/After) then OnRecord for each record in the DataSet.
I also included some helper functions to get values, set values and test if something has changed, note that I’m enforcing tight-coupling of the field accessors by using the SObjectField type.
Then I modified the execute function to call these event handlers in the appropriate circumstances.
So after all that …. How does a typical trigger handler now look?
I’ve shown the usage for each of the “features” as part of the above Lead trigger, however in reality (if we were only changing Web to Website or Social Media) then the implementation could have been reduced to the below
I hope this framework is a better model than the more traditional approach, there are still a couple of features that I considered but didn’t implement – changing the OnRecordEvent to a list of OnRecordEvents and maybe doing the same with the BeforeDataSet but at some point its tinkering for the sake of it!
Feel free to reach out and let me know if this is helpful or not!