Skip to content
Home » Blog » Wasting Time in Disabled fflib Trigger Handlers

Wasting Time in Disabled fflib Trigger Handlers

Did you know that you can dynamically disable fflib_SObjectDomain Trigger Handlers? You did? Ok then, did you know that when you do disable them their constructors run anyway? So be careful not to put any (expensive) logic inside those.

For instance, I had some common related records that needed to be loaded from the Database in multiple contexts. Something like getting the related Accounts in the Contact insert and update operations to do some custom validation. I collected Ids and fetched the records in the constructor so as not to duplicate the code.

My real scenario was of course a lot fancier (and expensive), but to illustrate:

public with sharing class ContactsTriggerHandler extends fflib_SObjectDomain {
    private Map<Id, Account> accountMap;

    public ContactsTriggerHandler(List<Contact> contacts) {
        super(contacts, Contact.sObjectType);
        Set<Id> accountIds = new Set<Id>();
        for (Contact contact : contacts) {
            accountIds.add(contact.AccountId);
        }
        accountMap = TriggerContextCache.getAccounts(accountIds);
    }

    public override void onValidate() {
        checkAccountNotBlocked();
    }

    public override void onValidate(Map<Id, SObject> existingRecords) {
        checkAccountNotBlocked();
    }

    private void checkAccountNotBlocked() {
        for (Contact contact : (List<Contact>) getRecords()) {
            if ('Blocked' == accountMap.get(contact.AccountId)?.Description) {
                contact.addError('Account is blocked');
            }
        }
    }

    public class Constructor implements fflib_SObjectDomain.IConstructable {
        public fflib_SObjectDomain construct(List<SObject> sObjectList) {
            return new ContactsTriggerHandler(sObjectList);
        }
    }
}

I had some frequent logic that didn’t need to be subject to validation or any other processing that was handled by the triggers. So to save on execution time (especially with large imports) it did its DMLs after disabling the triggers.

Id accountId = [SELECT Id FROM Account LIMIT 1].Id;

fflib_SObjectDomain.getTriggerEvent(ContactsTriggerHandler.class).disableAll();
insert new Contact(LastName = 'Test', AccountId = accountId);

It didn’t seem to save as much time as I thought it would, because some of the big SOQL queries (and more) were running anyway. Look at the logs from my simplified example: the Account SOQL query was executed even though I don’t need it. This could mean a lot of wasted (CPU) time in more complex situations.

Moving the cache loading out of the constructor helps with that. It may just need a little more love to make it elegant sometimes.

And be careful where it is placed as each Trigger Event calls different methods (e.g. onApplyDefaults() only on insert but before onValidate()). You could run into some NullPointer Exceptions.

public with sharing class ContactsTriggerHandler extends fflib_SObjectDomain {
    private Map<Id, Account> accountMap {
        get {
            if (accountMap == null) {
                Set<Id> accountIds = new Set<Id>();
                for (Contact contact : (List<Contact>) getRecords()) {
                    accountIds.add(contact.AccountId);
                }
                accountMap = TriggerContextCache.getAccounts(accountIds);
            }
            return accountMap;
        }
        set;
    }

    public ContactsTriggerHandler(List<Contact> contacts) {
        super(contacts, Contact.sObjectType);
    }

    ...
}

Running the same script again and now the SOQL query is no longer executed. Happy days!

Other Traps

Accidental Permissions

Apr 26, 202402 min read

Blowing the “Single Profile” approach out of the water When you install a package from AppExchange via the browser you get to choose whether to Install it for All Users, Only Admins, Specific Profiles or no-one. This really means if you want to grant full access to Classes, Pages, Fields…

UoW and SObject Instances

Oct 28, 202314 min read

I previously talked about the need to be careful about how you register records as dirty with the Apex Common’s Unit of Work. There is another little gotcha to be ware of I recently ran into. Though when it does happen to you then you are probably doing something you…

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x