Trigger Best Practices
One Trigger Per Object
A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts. Many times, the order of execution doesn’t matter but when it does matter, it’s nearly impossible to maintain proper flow control. A single Trigger can handle all possible combinations of Trigger contexts which are:
before
insert
after insert
before update
after update
before delete
after delete
after undelete
So as a best practice, create one Trigger per object and let it handle all of the contexts that you need. Here is an example of a Trigger that implements all possible contexts:
trigger trgContact on Contact (before insert, before update, before delete,after insert, after update, after delete, after undelete) {
// trigger body
}
Logic-less Triggers
Another widely-recognized best practice is to make your Triggers logic-less. That means, the role of the Trigger is just to delegate the logic responsibilities to some other handler class. There are many reasons to do this. For one, testing a Trigger is difficult if all of the application logic is in the trigger itself. If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org. Good old OO principles tell us that this is a bad practice. And to top it all off, cramming all of your logic into a Trigger is going to make for a mess one day. To remedy this scenario, just create a handler class and let your Trigger delegate to it. Here is an example:
trigger trgContact on Contact (after insert) {
ContactTriggerHandler.handleAfterInsert(Trigger.new);
}
And the handler class:
public class ContactTriggerHandler {
public static void handleAfterInsert(List opps) {
// handler logic
}
}
Context-Specific Handler Methods
One best-practice is to create context-specific handler methods in my Trigger handlers. In the above example, you’ll see that I’ve created a specific handler method just for after insert. If I were to implement new logic that ran on after update, I’d simply add a new handler method for it. Again, this handler method would be in the handler class, and not the Trigger. In this case, I might add some very light routing logic into the Trigger itself just so that the correct handler method is invoked:
trigger trgContact on Contact(after insert, after update) {
if(Trigger.isAfter && Trigger.isInsert) {
ContactTriggerHandler.handleAfterInsert(Trigger.new);
}
else if(Trigger.isAfter && Trigger.isUpdate) {
ContactTriggerHandler.handleAfterInsert(Trigger.new, Trigger.old);
}
}
Why Use a Framework?
Flow Control
Recursion Detection and Prevention
Centralize Enable/Disable of Triggers
Simplify testing and maintenance of your application logic
Basic Implementation
Trigger
trigger trgContact on Contact(after insert, after update, after delete, after undelete) {
new ContactTriggerHandler ().run();
}
And here is the handler class we will create:
public class ContactTriggerHandler extends TriggerHandler {
public OpportunityTriggerHandler() {}
/* context overrides */
protected void override beforeUpdate() {
setLostOppsToZero();
}
/* private methods */
private void setLostOppsToZero(List) {
for(Opportunity o : (List<Opportunity>) Trigger.new) {
if(o.StageName == 'Closed Lost' && o.Amount > 0) {
o.Amount = 0;
}
}
}
}
**An example Trigger and Handler Class is enclosed that implements all the trigger context.
public class ContactTriggerHandler extends TriggerHandler {
public ContactTriggerHandler () {}
/* context overrides */
protected void override beforeUpdate() {
setSomeValues();
}
protected void override afterInsert() {
doSomeAfterInsertStuff();
}
protected void override beforeDelete() {
doSomeStuffBeforeDelete();
}
/* private methods */
setSomeValues(){}
doSomeAfterInsertStuff(){}
doSomeStuffBeforeDelete(){}
}
As you can see, the resulting Trigger handler class is easy to understand and easy to update when your requirements change.
The handler methods like afterInsert() are defined logic-less and meant to be overridden. If they aren’t overridden, nothing really happens. Here’s what one of those methods look like in the TriggerHandler class:
Recursion Protection
In this framework, we provide a utility that can prevent a single TriggerHandler from firing recursively. The implementation is pretty simple and allows you to set the number of executions per Trigger:
public class ContactTriggerHandler extends TriggerHandler {
public ContactTriggerHandler () {
this.setMaxLoopCount(1);
}
public override void afterUpdate() {
List <Contact> con = [SELECT Id FROM Contact WHERE Id IN :Trigger.newMap.keySet()];
update con; // this will throw after this update
}
}
A Bypass API?
public class ContactTriggerHandler extends TriggerHandler {
public override void afterUpdate() {
Contact con = [SELECT Id, AccountId FROM Contact WHERE Id IN :Trigger.newMap.keySet() LIMIT 1];
Account acc = [SELECT Id, Name FROM Account WHERE Id = :opps.get(0).AccountId];
Case c = new Case();
c.Subject = 'My Bypassed Case';
TriggerHandler.bypass('CaseTriggerHandler');
insert c; // won't invoke the CaseTriggerHandler
TriggerHandler.clearBypass('CaseTriggerHandler');
c.Subject = 'No More Bypass';
update c; // will invoke the CaseTriggerHandler
}
}
On/Off Switch
Every time a trigger runs the trigger handler framework creates a record for its trigger Handler under TriggerHandler__c custom setting, To switch off a specific trigger just open the Trigger manager, de-activate the trigger and save.
More information here : https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices