2

Recently I was tasked with a project to dynamically call a class from a packaged trigger. This will allow clients to hook their custom logic instead of what is supplied as part of the package.

I started old school way by creating interface and abstract methods and it got ugly pretty quickly. It would not scale and hence looked further into what native options are available within the platform. I bumped into Callable Interface which did the trick. Please see below solution for step-by-step instructions

References

  1. https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_interface_System_Callable.htm
  2. Call Apex class method on the fly (dynamically)
1
  • 2
    I love seeing a Q&A, except when it's not written like a Q&A. Could you please phrase the question in the form of a question (e.g. describe the situation such as "How do I make trigger logic dynamic?") and present a use case (e.g. "We were trying to find a way to call subscriber code from a managed package trigger. What would be an easy way to do this?") Do not literally use this, it should be more detailed. Your question should follow the How to Ask guidelines. Your answer looks great, though.
    – sfdcfox
    Mar 16 '20 at 20:23
4

Here is a snippet of fully functional and dynamic execution of Apex classes, methods, and arguments. Other advantages of using this pattern

  • Control order of execution
  • Toggle what features/functionality to run

Step 1: Create a metadata, let's say Trigger Settings (API name: TriggerSettings__mdt)

enter image description here

Step 2: Create a Utils class to pull the metadata records based on sObject and Active flag.

public with sharing class Utils {

public static Map<String, List<TriggerSettings__mdt>> getTriggerSettings(String sObj) {

if(String.isEmpty(sObj))
    return null;

Map<String, List<TriggerSettings__mdt>> mapTriggerSettings = new Map<String, List<TriggerSettings__mdt>>();

for(TriggerSettings__mdt setting : [SELECT ClassName__c, MethodName__c, Event__c 
                                    FROM TriggerSettings__mdt 
                                    WHERE IsActive__c=true AND SObjectType__c=:sObj 
                                    ORDER BY Order__c]) {

    if(mapTriggerSettings.containsKey(setting.Event__c))
        mapTriggerSettings.get(setting.Event__c).add(setting);
    else
        mapTriggerSettings.put(setting.Event__c, new List<TriggerSettings__mdt>{setting});
}

return mapTriggerSettings;

 }
  public class ExtensionMalformedException extends Exception {}

}

Step 3: Create Trigger Helper class that implements Callable interface. This class will map the method name in the metadata and dispatch actual method(s). If you are creating a package, this class will be part of the package. But clients can provide their own implementation by creating new class that will implement Callable Interface that will dispatch custom methods/logic. Make sure to update the TriggerSettings metadata with correct class name and method name if the logic needs to be over-written

public class CaseTriggerHelper implements Callable {

public static void method1(List<Case> caseList) {
    //Do Something
}

public static void method2(List<Case> caseList, Map<Id, Case> newMap, Map<Id, Case> oldMap) {
    //Do Something
}

public static void method3(List<Case> caseList, Map<Id, Case> oldMap) {
    //Do Something
}

public static void method4(List<Case> caseList) {
    //Do Something
}

public static Object call(String action, Map<String, Object> args) {

    //To get event type, use => args.get('eventType') 

    switch on action {

        when 'method1' {
            method1(
                Trigger.new
            );
        }
        when 'method2' {
            method2(
                Trigger.new,
                (Map<Id, Case>)Trigger.newMap,
                (Map<Id, Case>)Trigger.oldMap
            );
        }
        when 'method3' { 
            method3(
                Trigger.new,
                (Map<Id, Case>)Trigger.oldMap
            );
        } 
        when 'method4' {
            method4(
                Trigger.old
            );
        }
        when else {
            throw new Utils.ExtensionMalformedException(action + ' not implemented');
        }
    }

    return action + ' dispatched successfully. Event: ' + args.get('eventType');
}
}

Step 4: From here on it is business as usual. Create TriggerHandler and the sObject Trigger

public class CaseTriggerHandler {

Map<String, List<TriggerSettings__mdt>> triggerSettings;

public CaseTriggerHandler() {
    triggerSettings = Utils.getTriggerSettings('Case');
}

public static Boolean caseRecursiveCheck = false;

public void handleEvent(String eventType) {

    if(caseRecursiveCheck) {
        return;
    }

    if(triggerSettings == null || !triggerSettings.containsKey(eventType)) {
        return;
    }

    for(TriggerSettings__mdt setting : triggerSettings.get(eventType)) {

        String className = setting.ClassName__c;
        String methodName = setting.MethodName__c;

        Callable extension = (Callable) Type.forName(className).newInstance();
        Object result = extension.call(methodName, new Map<String, Object> {
            'eventType' => eventType
        });

        system.debug('callable result ' + result);
    }
}
}

Step 5: Create sObject Trigger

trigger CaseTrigger on Case (before insert, before update, before delete, after insert, after update, after delete) {

CaseTriggerHandler handler = new CaseTriggerHandler();

if(Trigger.isInsert) {
    if(Trigger.isBefore) 
        handler.handleEvent('BeforeInsert');
    else if(Trigger.isAfter)
        handler.handleEvent('AfterInsert');
} 

else if(Trigger.isUpdate) {
    if(Trigger.isBefore)
        handler.handleEvent('BeforeUpdate');
    else if(Trigger.isAfter)
        handler.handleEvent('AfterUpdate');
}

else if(Trigger.isDelete) {
    if(Trigger.isBefore)
        handler.handleEvent('BeforeDelete');
    else if(Trigger.isAfter)
        handler.handleEvent('AfterDelete');
}
}

Finally the magic.

Create a new case enter image description here

Update a case enter image description here

Delete a case enter image description here

Hope this helps someone looking to build dynamic apex execution. Open to ideas on how it can be further improved.

1
  • This is an interesting pattern, have you pulled into a public github repo? Jul 8 '20 at 21:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.