Optimize Apex Triggers

How to optimize Apex Triggers?

1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks

Do you struggle with poor performance during Trigger Execution? Is your code hitting any of the platform limits? You can improve it by using one of the following strategies:

1. Refactoring 2. Checking entry criteria 3. Moving to asynchronous tools 4. Using frameworks

Refactor

The first step that we can take to optimize triggers is to check if our code can be improved. You should start by bulkifying all methods. Next, identify unnecessary code executions, duplicates or other simple mistakes.

Don't do this
public class AccountTriggerHandler {

    public static void beforeInsert(List<Account> newAccounts) {
        for ( Account account: newAccounts ) {
            try {
                AccountUtils.checkForCompetition(account);
            } catch (Exception e) {}
        }
    }
}

public class AccountUtils {

    public static void checkForCompetition(Account account) {
        for(Lead lead: [SELECT Name FROM Lead WHERE Name LIKE account.Name]) {
            ...
        }
    }
}

Delegating parts of the logic to external classes is generally considered good practice. However, it can also be risky. While the previous example is an exaggeration, such delegation can lead to issues like SOQL/DML operations inside loops or nested loops, which can negatively impact trigger performance.

Refactor

All trigger methods should process more than one record at the time.

Do this
public class AccountTriggerHandler {

    public static void beforeInsert(List<Account> newAccounts) {
        List<Account> checkForCompetition = new List<Account>();

        for ( Account account: newAccounts ) {
            if(account.RecordType.Name == 'Partner'){
                checkForCompetition.add(account);
            }
        }

        AccountUtils.checkForCompetition(checkForCompetition);
    }
}

public class AccountUtils {

    public static void checkForCompetition(List<Account> accounts) {
        ...
    }
}

Optimize entry criteria

The next important step is adding or improving entry criteria. It is applicable for both, trigger records and all SOQL queries you make.

Do this
public class AccountTriggerHandler {

    public static void beforeInsert(List<Account> newAccounts) {
        for ( Account account: newAccounts ) {
            if(account.RecordType = 'Partner') {
                AccountUtils.checkForCompetition(account);
            }
        }
    }
}

It is important to ensure that your code only executes for the necessary set of records, instead of the entire data set. The same goes for queried records, you should add a filter to make sure only needed data is returned.

Escape with asynchronous

Another way is to move parts of the logic to asynchronous execution. Every action that is complicated, or when we don’t need instant results, can be moved out of the original trigger transaction.

Do this
public class AccountTriggerHandler {

    public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
        List<Id> emailChanged = new List<Id>();

        for ( Account account: newAccounts ) {
            if(account.Email != oldAccounts.get(account.Id).Email){
                updateStakeholder.add(account.Id);
            }
        }

        AccountUtils.updateStakeholder(emailChanged);
    }
}

public class AccountUtils {

    @Future
    public static void updateStakeholder(List<Id> accounts) {
        ...
    }
}

Use Enterprise Patterns

The last piece that you can use to improve the situation is to use some frameworks that implement enterprise patterns. In case of triggers, you can use Unit of Work.

Don't do this
public class AccountTriggerHandler {

    public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
        ...

        uow.registerDirty(contact);

        ...
        uow.registerDirty(case);

        ...
        uow.commitWork();
    }
}
Do this
public class AccountTriggerHandler {

    public static void afterUpdate(List<Account> newAccounts, Map<Id, Account> oldAccounts) {
        ...

        uow.registerDirty(contact);

        ...
        uow.registerDirty(case);

        ...
        uow.commitWork();
    }
}

Text and code were extracted from the original slide. Plain-text version of the whole catalog