Apex Trigger Best Practices

Sharing is Caring

Apex Triggers are very powerful and are easily one of the most important parts of Salesforce because they significantly impact all database operations (inserts, updates, deletes, etc. ) With this incredible power and flexibility there’s a lot of room for problems. In my career as a Salesforce developer, I’ve looked at hundreds of different apex triggers and realized there are a lot of points of failure with apex triggers. This post is a very high level list of best practices and why they are important for apex triggers.

I don’t believe that anybody goes out of their way to intentionally create bad triggers. Triggers usually start out very small and expand over time as they expand, we need to understand and constantly improve them. I hope to expand on this post a lot more over time and provide examples of how to refactor a trigger because it’s hard to improve if you don’t understand there’s a problem and what the alternatives are.

Before reading this post, I recommend that you understand how triggers function. For example, you need to understand how before and after triggers work and have a decent understanding of the Salesforce Order of Execution.

Use a Trigger Framework
Trigger Frameworks help significantly reduce the amount of development effort involved in creating and maintaining triggers. Trigger Frameworks offer a ton of other benefits like Enforcing Consistency of Trigger Logic, simplify testing, and helping conform to Apex Trigger Best Practices.

Trigger Frameworks offer a centralized and standardized way of providing a trigger kill switch and detecting recursive triggers. Some of the better trigger frameworks even can detect if they are being called recusively and allowing some to be called recursively. Kevin Poorman in his book Master Application Development with Force.com covers Trigger Frameworks quite extensively. I’ve also put together a post about Choosing Apex Trigger Frameworks and some of the things you need to consider.

Objects Should Have As Few Triggers as Possible
Ideally, a Salesforce object should only have one trigger because it makes it easier to predict the order in which operations will be conducted. In his book, Advanced Apex Programming, Dan Appleman coined the phrase “One Trigger to Rule Them All” to explain this concept.

In general, I think it’s truly a best practice even though it is a bit controversial and can be difficult for somebody nontechnical to figure out what’s going on. In some situations, it may make sense to split up triggers based on the different trigger contexts. For example, a delete trigger probably doesn’t need to do any of the same operations that an insert or update trigger would need to do.

Triggers should contain no logic
Triggers should contain absolutely no logic and should instead called an apex class that can handle the Trigger.new list or the Trigger.OldMap. By separating this logic into another class and calling it we can follow the separation of concerns principle which means the code and trigger will be more maintainable and possibly easier to debug.

By moving trigger logic into a separate class, we are able to unit test a lot easier because we don’t necessarily need to initiate DML operations and we can handle lots of scenarios which may not technically be valid right now. Small separate functions also allows us to fully unit test and do integration tests on the trigger.

Code Called by Triggers Must Have Great Code Coverage
The code that the trigger uses must have a lot of code coverage that contains asserts and tests each of the logic paths. An exception being thrown in a trigger can cause records not to be inserted or updated, debugging the code can provide incredible difficult, and as most developers know deploying apex to Salesforce can take a very long time.

Use Collections like Sets, Maps, and Lists
All triggers execute in a batch context which meands that when a trigger fires it could update multiple records at the same time. It’s incredibly important to use Apex Collections to query data and store data. By using the proper collection it’s also possible to create extremely efficient queries and for loops.

Maps are extremely efficient for storing data and find elements without having to use for loops. Sets can easily be used to check for duplicates ad to reduce the number of duplicates. I’ve written about how to convert lists to sets and maps and strongly recommend understanding the concepts.

Bulkify All Code
It’s incredibly important that all code called by an apex trigger can handle 200 records. For small Salesforce orgs, this doesn’t seem like a problem but it can be a problem if you or your admins are using the data loader frequently.

Code that looks like this needs to be changed, because it will use too many soql queries and won’t perform all that well

for (Account acc : Trigger.new) {
	User usr = (User)[select Id, FirstName, LastName from User Where Id =: acc.OwnerId];
	doSomething(acc, usr);

The change needs to be something more like this:

Set<Id> ownerIds = new Set<Id>();
for (Account acc: Trigger.new) {

Map<Id, User> users = new Map<Id, User>([select Id, FirstName, LastName from User Where Id IN: ownerIds]);

for (Account acc : Trigger.new) {
	User usr = users.get(acc.OwnerId);
	doSomething(acc, usr);

Avoid hardcoding Ids
Hardcoding Ids inside of trigger logic or code anywhere else is a bad idea because it’s difficult to maintain and often the Ids are different between environments. If you require an Id for something you should store the values in a Custom Setting or Custom Metadata Type.

Check back in the future, I plan to provide additional blog articles about Apex Triggers as I feel they are an essential part of the Salesforce architecture.

Sharing is Caring

Brian is a software architect and technology leader living in Niagara Falls with 13+ years of development experience. He is passionate about automation, business process re-engineering, and building a better tomorrow.

Brian is a proud father of four: two boys, and two girls and has been happily married to Crystal for more than ten years. From time to time, Brian may post about his faith, his family, and definitely about technology.