Apex triggers are an essential part of development in Salesforce. Since triggers can get very complicated, I’ll be sharing a framework that follows the best practices with a little twist.
To understand the basics, I recommend reading the documentation Salesforce has for apex triggers. For those that are more experienced with developing on the Salesforce platform, you know that the best practice is to have one trigger per object. Another good practice is to have a handler class that holds all of the logic for a single trigger. (More information on best practices for triggers can be found here.)
Well, I’m going to suggest one more step to enhance the Trigger Framework. My version consists of three components:
- Object Trigger
- Trigger Handler Class
- Trigger Helper Class
The Explanation
The object trigger will remain as a logic-less class. All it will do is determine the type of trigger, and redirect the data to the right function in the handler class.
The handler class will also be a logic-less class. This is key to this framework setup. The purpose of the handler class is to determine the flow of the logic. In the example below, I have set up the framework for the Opportunity object. You will see two functions: one that updates the opportunity’s owner, and another that updates the opportunity’s type.
With this new framework, it’s very clear to see what exactly is happening when a new opportunity is created just by looking at the handler class. The name of the function describes what it is doing, and it will be easy to modify the flow, or disable a function. Let’s say, you need to update the type before updating the owner, just swap the two lines and done. Or, you need to stop re-assigning the opportunities, just comment out the call and done. Pretty easy, right?
The last piece is the helper class. This will hold all of the logic for the trigger. Each function should only be created for a single purpose. If a function is too complicated, or is performing multiple tasks, then it should be split up.
Now that you have all of that, let’s see an example!
The Code
OpportunityTrigger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * Name: OpportunityTrigger * Type: Trigger * Description: Handles all trigger events of insert and update * for before and after for the Opportunity object. */ trigger OpportunityTrigger on Opportunity (before insert, before update, after insert, after update) { if(Trigger.isBefore) { if(Trigger.isInsert) { OpportunityTriggerHandler.onBeforeInsert(Trigger.new, Trigger.newMap); } else if(Trigger.isUpdate) { // make a call to OpportunityTriggerHandler here for on before update } } else { if(Trigger.isInsert) { // make a call to OpportunityTriggerHandler here for on after insert } else if(Trigger.isUpdate) { // make a call to OpportunityTriggerHandler here for on after update } } } |
OpportunityTriggerHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * Name: OpportunityTriggerHandler * Type: Class * Description: This class is used by OpportunityTrigger to call * the helper functions depending on the type of * event that was triggered. */ public class OpportunityTriggerHandler { public static void onBeforeInsert(List<Opportunity> newOpptys, Map<Id, Opportunity> newOpptysMap) { OpportunityTriggerHelper.matchOpptyOwnerToAcc(newOpptys); OpportunityTriggerHelper.setTypeBasedOnOwner(newOpptys, newOpptysMap); } } |
OpportunityTriggerHelper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /** * Name: OpportunityTriggerHelper * Type: Class * Description: This class is used by OpportunityTriggerHandler * and holds all of the functions that will be used * in a trigger event. */ public class OpportunityTriggerHelper { /* Updates the owner of the new opportunities to match * the owner of the account that the opportunities belong to. */ public static void matchOpptyOwnerToAcc(List<Opportunity> newOpptys) { // get the account ids from the new opportunities Set<Id> accIds = new Set<Id>(); for(Opportunity opp : newOpptys) { if(opp.AccountId != NULL) accIds.add(opp.AccountId); } // get the account records related to the new opportunities Map<Id, Account> accMap = new Map<Id, Account>( [SELECT OwnerId FROM Account WHERE Id = :accIds]); for(Opportunity opp : newOpptys) { // if the oppty does not have an account, skip if(opp.AccountId == NULL) continue; Account acc = accMap.get(opp.AccountId); opp.OwnerId = acc.OwnerId; } } /* Sets the type of the opportunity based on the profile * of the user that owns the opportunity. */ public static void setTypeBasedOnOwner(List<Opportunity> newOpptys) { // get the owner ids from the new opportunities Set<Id> ownerIds = new Set<Id>(); for(Opportunity opp : newOpptys) { ownerIds.add(opp.OwnerId); } Map<Id, User> userMap = new Map<Id, User>( [SELECT ProfileId FROM User WHERE Id = :ownerIds]); for(Opportunity opp : newOpptys) { User owner = userMap.get(opp.OwnerId); if(owner.ProfileId == CustomConstants.PartnerProfileId) opp.Type = 'Partners'; else if(owner.ProfileId == CustomConstants.CSRepProfileId) opp.Type = 'Renewal'; else opp.Type = 'New'; } } } |
Quick Tip
You may have noticed that I used a class called CustomConstants. For more information on how to use that, check it out here.
As always, if you have any questions or comments, please let me know!
