Triggers and Best Practice

Finally, I’m writing a blog post on best practice and it’s all around trigger frameworks.

The recommended approach to triggers is to implement triggers externalised from the underlying trigger i.e. in a separate class, and to have a single unit of code for each trigger.  I’ve written this within factories many times before and recently had a chance to review this approach one more time.

The reasoning for this approach is actually pretty well known in that according to the gospel the order of execution on multiple triggers is not guaranteed, therefore in organisations with complex trigger logic one unit of code will preserve the logical execution path.

So, lets kick off with a “recommended solution” before I apply a cynical approach!

First step is that we need to define an interface that each unit of code will implement, this is easy as we just map the trigger events. I’ve used a map as that has a finer grain (if you’re looking for a specific Id), rather than the list.  The trigger factory will take care of this later.

public interface ITriggerEvents {

void beforeInsert(map<id,sobject> mapNew);

void beforeUpdate(map<id,sobject> mapBefore, map<id,sobject> mapAfter);

void beforeDelete(map<id,sobject> mapAfter);


void afterInsert(map<id,sobject> mapAfter);

void afterUpdate(map<id,sobject> mapBefore, map<id,sobject> mapAfter);

void afterDelete(map<id,sobject> mapAfter);

void afterUndelete(map<id,sobject> mapAfter);


Implementation of this occurs in the Trigger Handler class, I’m adopting a naming convention of “TriggerHandler” object name.  For now, the implementation is very simple.

public class TriggerHandler_Lead implements ITriggerEvents{

public void beforeInsert(map<id,sobject> mapNew){


public void beforeUpdate(map<id,sobject> mapBefore, map<id,sobject> mapAfter){


public void beforeDelete(map<id,sobject> mapBefore){


public void afterInsert(map<id,sobject> mapAfter){


public void afterDelete(map<id,sobject> mapAfter){


public void afterUpdate(map<id,sobject> mapBefore, map<id,sobject> mapAfter){


public void afterUndelete(map<id,sobject> mapAfter){



So far so good, the trigger handler needs to support whatever “logic” is needed, and anything that is unique to the trigger should be placed into this class.

To implement the trigger, we simply call the trigger factory class passing a reference to the Trigger Handler class – note that this means the class must exist.

trigger Trigger_Lead on Lead (after delete, after insert, after undelete, after update, before delete, before insert, before update){



The Trigger Factory is where the fun starts, we instantiate an instance of the trigger class and then call the appropriate interface.

The approach to build out functionality should be that any domain (subject) specific functionality is built into the Trigger Handler class – anything that crosses this domain can either be abstracted to another class or better still abstracted into a base Trigger Handler class that each Trigger Handler extends.

This meets all of the architectural and best practice approaches.

Now, as always, here’s my objections!

The purpose of the abstraction is to remove complexity from triggers and therefore allow for reuse, however the abstraction alone provides a degree of complexity that is often more complex than implementing a simple trigger.

In addition, if we are abstracting complexity, if I were developing the trigger framework in a different platform Java, .Net, etc I would then use Reflection or Run Time Type Information (from my Delphi Days!) to hide the implementation of the factory – I would just query for all objects that supported the interface and create and call the appropriate version.

Finally, I detest the use of sobjects and even casting the sobject to its underlying type is a kludge.  The sole purpose of tight type coupling is to avoid the disaster of deleting a field that’s actually in use – something that sobject use often exacerbates.

Next post I’m going to try and remove some of these objections!


Leave a Reply

Your email address will not be published. Required fields are marked *