Preface – This post is part of the Object Oriented Thinking series.
Every Salesforce org with a strong development team always has just one trigger per object!
This practice is a prominent design pattern in Apex. Design patterns are basically templates you can use to solve common problems when coding. Think of them to be like best practices, like bulkification.
What is the “One Trigger per Object” design pattern?
It’s exactly as it sounds! You combine all possible triggers on a specific object into just one trigger.
How do you combine many triggers into one?
Instead of coding your logic directly inside a trigger, you code it separately in a class. Then in your trigger, you create an object of the class, then run your records through its logic. Repeat for all triggers!
What are the benefits of this design pattern?
The “One Trigger per Object” template
trigger MasterOpportunityTrigger on Opportunity ( before insert, after insert, before update, after update, before delete, after delete) {if (Trigger.isBefore) { if (Trigger.isInsert) {// Call class logic here! }if (Trigger.isUpdate) {// Call class logic here! }if (Trigger.isDelete) {// Call class logic here! } }if (Trigger.isAfter) { if (Trigger.isInsert) {// Call class logic here! }if (Trigger.isUpdate) {// Call class logic here! }if (Trigger.isDelete) {// Call class logic here! } } }
Here’s what’s happening in the template:
Example: Converting a trigger into a class
Now that we have our template, let’s take an existing trigger and convert it into a class.
We’ll use our trigger from Chapter 6 as an example:
// Check a checkbox only when an Opp is changed to Closed Won! trigger Winning on Opportunity (before update) { for (Opportunity opp : Trigger.new) { Opportunity oldOpp = Trigger.oldMap.get(opp.Id); Boolean oldOppIsWon = oldOpp.StageName.equals('Closed Won'); Boolean newOppIsWon = opp.StageName.equals('Closed Won'); if (!oldOppIsWon && newOppIsWon) { opp.I_am_Awesome__c = true; } } }
Converted into a class:
public class WinningOppChecker { // These variables store Trigger.oldMap and Trigger.newMapMap<Id, Opportunity> oldOpps; Map<Id, Opportunity> newOpps;// This is the constructor // A map of the old and new records is expected as inputspublic WinningOppChecker( Map<Id, Opportunity> oldTriggerOpps, Map<Id, Opportunity> newTriggerOpps) {oldOpps = oldTriggerOpps; newOpps = newTriggerOpps; } // The one method your master trigger will callpublic void checkWinningOpps() { for (Opportunity newOpp : newOpps.values()) { Opportunity oldOpp = oldOpps.get(newOpp.Id);Boolean oldOppIsWon = oldOpp.StageName.equals('Closed Won'); Boolean newOppIsWon = newOpp.StageName.equals('Closed Won'); if (!oldOppIsWon && newOppIsWon) { newOpp.I_am_Awesome__c = true; } } } }
Note that you’re always going to pass in Trigger.new (or newMap) into your converted class’s constructor because your class needs to know which records to act on. In this case we also pass Trigger.oldMap since we’re comparing old and new values.
To keep the implementation simple, your trigger should only need to call one method of your class.
Final Implementation
trigger MasterOpportunityTrigger on Opportunity ( before insert, after insert, before update, after update, before delete, after delete) { if (Trigger.isBefore) { if (Trigger.isInsert) { } if (Trigger.isUpdate) {// This post's example implemented in our master trigger! WinningOppChecker checker = new WinningOppChecker(Trigger.oldMap, Trigger.newMap); checker.checkWinningOpps();// Add other classes in your preferred execution order ClosedOppWelcomeEmailer emailer = new ClosedOppWelcomeEmailer(Trigger.new); emailer.sendWelcomeEmails(); } if (Trigger.isDelete) { } } if (Trigger.IsAfter) { if (Trigger.isInsert) { } if (Trigger.isUpdate) { } if (Trigger.isDelete) { } } }
I’ve you’ve made it this far, it’ll be impossible to tell the difference between you and a master Salesforce developer. People often say “fake it until you make it,” but at this point, you’re no longer faking anything. You are truly a Salesforce developer!
Next post: Introduction to the “static” keyword!
How about making methods as Static in Class and using them directly without creating instance of class .
This is a pattern I have used in my previous two companies. Thank you for sharing.
One question I had when I first implemented this that I did not find a lot of public opinion on was “What to do with the old triggers?”
Ex. If you have multiple triggers for one object and you refactor them into one with a handler class. What do you do with the other triggers? Delete them from the org? Disable them?
I have done both deleting and disabling and wonder what your opinion is.
I’ve set them as inactive in the past.
Does this change how you write your tests? I would think extracting out the class (in this case “WinningOppChecker”) would give you better isolation of what that piece of code is doing. However, I feel like because the trigger is still in the global context, it’s still executing as you write unit tests for WinningOppChecker. Do you write individual unit tests for the WinningOppChecker and pass in a list? Or do you just rely on the trigger executing and asserting that the record was modified as expected?
No changes to the test! Generally the test should focus on functionality, and it should be the same no matter how the code is implemented!
Great post. Would love to see that refactored using a switch on trigger.operationType.
One question that has been baffling me is how updates to a record field on isBefore is handled in a handler scenario? If you pass the trigger.new to the handler, and subsequently make a pre-insert update to a field, how does the trigger actually now that records were updated? It seems that passing the trigger.new means that the handler would have its own copy of the list of trigger records, and so updating fields is in the copy, not the actual.
Why is Map better than passing Trigger.New as an sObject ?
with Regards
Filip Poverud
You can get objects in a Map easier than in a List
A) Is a managed package’s trigger out of control?
B) Does Asynchronous Apex Triggers in Summer ‘19 change it to a “Two Triggers per Object” design pattern:
1. transaction-based logic in the “1” Apex object trigger.
2. resource-intensive business logic asynchronously in the “2” change event trigger; rehash the “Trigger per Object” template and use trigger context variables to isolate a portion of the “2” trigger to the only possible scenario (after insert).
https://releasenotes.docs.salesforce.com/en-us/summer19/release-notes/rn_change_event_triggers.htm
https://developer.salesforce.com/blogs/2019/06/get-buildspiration-with-asynchronous-apex-triggers-in-summer-19.html
Using this design pattern, how do you handle triggers that run in multiple events? i.e. before insert AND before update?
Do you simply repeat the instantiation of the object and call the method in both places?
Yup!
Man, that was MUCH faster than I expected! Thanks so much for the feedback, David!
I have another question for you regarding this.
I’m getting started on the final project on the google doc. I want to follow the One Trigger Per Object design pattern, but I am not sure how to actually use the first trigger zip code territories for both “before insert” and “before update”.
Before update makes sense, because I can pass in the existing oldTriggerMaps and newTriggerMaps. But if I want to use that trigger on “before insert” to handle non-null zip codes, the trigger maps aren’t available. Is there a better way to go about constructing my trigger to accommodate both instances?
First Google search result, no question on that.
Thank you, Master David, for teaching us the power of the force and great architecture! Even I am a solo developer, I should do it the right way.
Hi David,
The one thing I don’t understand and it’s really hindering me regards to working within a class with a similar framework at a new job, is…
How does Map oldOpps and Map store trigger.newMap and trigger.oldMap?!
They seem like completely arbitrary declarations to me. How do they have anything to do with trigger variables?
Your help is very much appreciated (and needed)! Thanks!
It looks like my message didn’t post completely correctly. “Map oldOpps and Map” is supposed to be “oldOpps and newOpps”.
Thanks
They store it via the constructor =)
But please let us know how below variables have mapped the Opportunities
corresponding to Opportunity Ids
Map oldTriggerOpps,
Map newTriggerOpps
But how the values are set for below Maps
Map oldTriggerOpps
Map newTriggerOpps
On the flip side, this pattern introduces a single point of failure, if anything fails…other trigger methods may not run or may also fail. You also lose the ability to enable/disable separate trigger operations. You also simply cannot disable one operation, all trigger operations will be down until fixes are deployed. There is something to be appreciated with separation and loose coupling are basic OO design principles may prevail in some scenarios.
Check this doc out!
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_transaction.htm
Basically, all methods fail in a transaction if one of the methods fails. This happens whether you use this one trigger pattern or not! Note that all triggers are included in a transaction (insert, update, delete), they are not separated out.
Give this pattern a try, I think you’ll like it!
I remember a previous coworker mentioning this, but didn’t know the specifics. I read this and was grateful for such a clear, succinct explanation of it … with examples! Then I realized it was your post, David Liu. Well, that figures. Always, the master appears! Many thanks
Thank you Wenda!
Hi, David, I have been working on this for several days. I have a trigger that looks like this:
// Gets the Territory Name from the Zip_Code_Map__c object by Postal Code on Leads. Lead assnt rules then use these territories
trigger GetLeadTerr on Lead (before insert, before update) {
//Get a set of all the zip codes of Leads to query
Set allZips = new set();
for (Lead theLead : Trigger.new) {
if (theLead.PostalCode != null)
if (theLead.Country == ‘United States’) {
allZips.add(theLead.PostalCode); }
}
//Query for all the zip codes in the previous section (querying the zip code map object)
List zipTerr = [SELECT Id, Zip_Code__c FROM Zip_Code_Map__c
WHERE Zip_Code__c IN :allZips];
//Create map to search for zip codes by Zip
Map zipToLeadMap = new Map();
for (Zip_Code_Map__c z : zipTerr) {
zipToLeadMap.put(z.Zip_Code__c, z);
}
//Get the matching Lead in the Map by Zip
for (Lead theLead : Trigger.new) {
if (theLead.PostalCode != null) {
Zip_Code_Map__c gotTerr = zipToLeadMap.get(theLead.PostalCode);
if (gotTerr != null) {
theLead.Zip_Lookup__c = gotTerr.Id;
}
}
}
}
I am trying to use a generic trigger and put the above code in a class … can you please let me know the basics for how I should call the class from the trigger? What do I pass? Do I call the class in every possible area (insert, update) where I need it to fire? What do I pass into the class from the trigger as parameters?
Thanks so much for any insights. w
All of your assumptions are 100% right!
In terms of parameters, for example, since your code uses Trigger.new, you’ll have to pass that.
How would we pass Trigger.New or Trigger.Old vs Trigger.NewMap/Trigger.OldMap in the constructor?
Just as List inputs, for example!
Take this concept one step forward… move your design pattern into admin controlled configuration and replace all you trigger code with a single line -> GearsBase.GearsExecute.executeAllMethods();
User Guide, Technical Guide, and free (yup $0) managed package available at http://gearscrm.com/about/apex-development-approach/trigger-architecture-faqs/trigger-architecture-resources/trigger-architecture-downloads/
Why pass trigger variables? You can remove those from the method signature.
So you know what records to update!
One trigger to rule them all and now you stop with the Lord of the Rings references? :)
Hi David, as has been said…Thank You! Question:
So can you let me know if I’m understanding the process(abreviated below for the bits that are confusing to me):
1. oldOpps and newOpps contain the records being passed in.
2.The loop checks the box for all relevant records and puts them into newOpp
3. Each newOpp is passed back to the trigger list -checker-
4. and when the loop is done, the DML op is done with the trigger list- and if this was a cross object query there would have to be an explicit DML statement in the trigger, after the method call
???
Also, thanks for your awesome pluralsight tutorial. I cant wait for the next installment. I gave you your 39th 5 out of 5 stars ;) So good.
I asked this question below but realized it was incomplete, so delete that one
Thank you for taking the class GG!
Let me elaborate a little bit on your bullets:
1. oldOpps and newOpps contain the records being passed in.
Correct, oldOpps is the versions of the records before they are changed, and newOpps are the latest versions.
2.The loop checks the box for all relevant records and puts them into newOpp
We specifically check of newOpp records that have different values than their oldOpp versions
3. Each newOpp is passed back to the trigger list -checker-
Yup!
4. and when the loop is done, the DML op is done with the trigger list- and if this was a cross object query there would have to be an explicit DML statement in the trigger, after the method call
Right on – well done GG!
Great Site! I appreciate that you can’t reply to every question posted, but there is just one thing I can’t seem to find good information on anywhere, so I am hoping for you can reply! If I have one trigger per object, and I am calling methods that have SOQL queries in them, and those methods are called in the trigger for loop, I am going to hit a query limit. So my question is, in this scenario where you have a trigger doing no logic, but just calling methods in classes, where do you do your SOQL queries?
You can still have one trigger per object and not hit the SOQL limit!
Basically, in your class, do your SOQL query before you do your logic in a loop.
Superb…. very nicely & softly explained
Thanks!
Hi David
Awesome post. I used it. But I ran into a trouble down the line. We all know trigger.newMap does not work for BEFORE INSERT, it remains NULL for this trigger event. So when I tried to pass trigger.newMap in the class constructor, the value of newMap came out to be null. For other trigger events, trigger.newMap isnt null when this pattern is used. If you or anyone came across this, kindly share your experience.
Cheers,
Vishal Khanna
It’s OK if it’s null =) Just handle that scenario in your class!
This is the one that I suggested to a Technical Architect.He said absolutely no code in trigger other than the class call.
Also if for before insert and before update the classes are different.Wouldn’y that increase the overhead to write and duplicate code ?
What if we have the same code for insert and update(before and after)
There are many variances to this pattern, all of them have their strengths and weaknesses =)
Don’t forget you can always pass on the Trigger.isInsert and Trigger.isUpdate variables into your classes too!
How to pass the Trigger.isInsert and Trigger.isUpdate to apex class.I am having the trigger where updating the field with same value in both cases and wnated to mange the same in single class.I want to pass the event to apex class.
Thanks a lot David! You are Owesome
As usual you explain complex things in a way that is easier to understand!
I just have one question. Are there any other design patterns specific to Salesforce? Are there any books dedicated to design patterns on salesforce/Apex/Visualforce?
It would be great if you could share us any online links for salesforce design patterns. Thanks!
Pingback: blog.pl.4hotele.com
David, could you please also provide, how to write tests on this pattern, should we write two tests, one for trigger and one for handler? Thanks
One test(class) should be enough. If you test your handler it will test your trigger, as all the trigger does is pass the trigger variables to the handler.
Of course you should write multiple tests for the handler, testing various scenarios, but the trigger it self will be tests by the handler tests.
A great design pattern…. thanks for sharing it!
Thank you!
Very Simple but so effective. Helped me a ton to redesign the way I was writing triggers and getting confused in complex handling. Thanks a ton!
Hey David,
Lovely explanation. Hoping you could clarify a real-world design problem for me:
I’m working a trigger that will create permission sets for community users based on three boolean checkboxes on the user page. The context is after insert and after update.
The quick and dirty logic is:
Trigger.isUpdate? IF yes, then loop through and delete any permission set assignments for that user record (in a bulkified fashion, of course)
Then, create permission sets and add to a list to be inserted once we’ve looped through Trigger.new
I need to put the dml in a separate class anyways to deal with the Mixed DML restrictions, and I’m trying to think through how I could use the One Trigger Per Object design pattern to do this in a nice and logical way.
My first thought was that I could just call two methods in the isUpdate context (delete and create) and only call the create method in the isInsert context. It seems like that would work, but I’d like to see if a more elegant solution exists.
any ideas?
Try solving this one without using two methods/classes in the trigger! (Unless you’re doing two completely separate and unrelated things)
Have you considered passing in your trigger context as a variable to your classes?
Good luck!
Thanks, David. I think I see what you’re saying — pass the isUpdate as a boolean into the handler class and let that class handle the logic of figuring out which methods need to be called? I like that idea and it seems like it would be more object oriented to do so.
Right now I have it working by calling the two methods (which invoke other @future methods), but I think I’m going to try and implement this before I migrate to production.
Thanks for the blog. It’s been a huge source of inspiration, and I look forward to being able to buy you a beer at dreamforce. :-)
Right on =) I’ll take you up on that beer!
David
What are the pros and cons between these two Architectures of ‘One Trigger Per Object’ ?
1) Your way — where you are instantiating the Trigger variables in constructor of the helper class. All 4 Trigger.New, Trigger.Old, Trigger.NewMap and Trigger.OldMap will have to be instantiated and are available for everything inside the class and if you are not using one or more, you simply pass Nulls or pass all 4 and let them sit inside the class, whether we use it or not.
2) Only pass those variables, which are required to the helper class.
trigger AccountTrigger on Account {
TriggerHelper TH = new TriggerHelper();
if (isBefore) {
if (isInsert) {
TH.onBeforeInsert(Trigger.new);
}
}
}
Either way works! You can also have multiple constructors to accept a flexible number of inputs.
In the end it really boils down to whichever is simpler for you (and your team) to understand.
Awesome! And clearly explained. One question though, could you also explain what to do if you need to run the logic on all instances. Do you have to put the 2 lines in all 6 possible filters in the trigger? Or is there another way?
Yup you got it!
Just re-add the two lines to the appropriate section =)
Instead of making an instance of the handler class in my trigger, I call the (static) handler class directly, something like this:
trigger TaskTrigger on Task (after insert) {
if (Trigger.isAfter) {
if(TaskTriggerHandler.firstRun == true){
TaskTriggerHandler.firstRun = false;
TaskTriggerHandler.sendLeadEmailAfterNoContact(Trigger.new);
}
}
}
Is creating an instance of the helper class better, and if so, could you please elaborate why?
Using static methods will work, but it is generally used as the exception instead of the default.
In most basic examples, the differences are indistinguishable other than you save a line of code using static methods. But in the long run, you lose some of the advantages of objects.
Objects are meant to be organisms. They are living, breathing entities that all come from a similar mold. While each object has the same template, different objects of the same class could be in different stages, and thus each object should be treated differently.
In a static world, there are no objects and no states. Just actions that happen with zero context. Many times this doesn’t matter, but when it does, if you’ve already coded everything to be static, it’s very difficult to make things object oriented again. Also, starting off static makes you less likely to think of things in an object oriented way, which could hurt the scalability of your code in the future.
Basically, play it safe and just go non-static. You only really lose a line of code!
Anyway, this site has a much better explanation:
https://r.je/static-methods-bad-practice.html
Just to add – static is meant to be used for global methods, aka things that never ever ever need to be in any object context no matter how far your imagination might stretch.
For example, a method that converts millimeters into centimeters. No matter where you are or what you’re doing, you always divide by 10 here.
I’ll probably have a post on this down the road. For now just play it safe and use objects.
Never thought of it this way. Shows I’m still only scratching the surface of programming. Anyway, thanks a lot for the clear explanation and the link!
Thank U realy for this answer!
I was asked this question during the interview, specifically “what are the pros and cons of creating entity class handler in the trigger compared to calling handler methods static?”. Now i understand that pros are – scalability and OO-patterns using, and
my questions is what are the other PROS and CONS of using ” new ObjectTriggerHandler().beforeInsert(Trigger.new);” in trigger on Object insted of “ObjectTriggerHandler.beforeInsert(Trigger.new);”
No words to express my gratitude.
Awesome blog
Great post David, this will definitely help me with one of my trigger I am trying to create!
Thanks David.. You are Excellent..
Wow!!!!! another great post. I completely agree with you in writing one trigger for one object. Moving your business logic to apex classes is always helpful and it also shows the maturity of the developer. Thanks for sharing your thoughts here.