Apex Trigger Framework: A Generic way to join Trigger Contexts.
One Trigger, many logics.
Let’s understand the problem first.
As we know, We can write multiple triggers on an object in salesforce. But it is not recommended by salesforce to have more than 1 trigger on an object. The reason is, you can’t fix the order of execution of these triggers. So, if you have more than one trigger in an object, which one will run first, no one knows.
The only possible solution which is probably the best is to club all logic in one trigger. Don’t go for another one. This solution will work fine, when you are working on small applications and that too unmanaged package deliveries. Since it’s unmanaged, you can modify and add code in existing triggers.
BUT….!!! When it comes to managed package deliveries, you won’t be able to modify the package triggers, and you would be left with the option of writing new triggers for other business use cases.
How would this Framework help?
This Framework allows us to keep a single trigger for each object, irrespective of unmanaged or managed deliveries. Customers can still be able to add their business logic in the triggers, without even writing new triggers.
The idea is to join all the trigger contexts of similar types, together. For example, all BEFORE (INSERT/UPDATE/DELETE) will run together, doesn’t matter, it’s a package trigger or custom trigger logic added by customers.
How is this possible? — The Join Framework
To make it possible, we need the help of Object-oriented Programming concepts, like Inheritance. Also, we use the concept of Registry here.
Let’s start with the design of the components,
An Abstract class looks like below
Except for the run method, all methods are virtual and are nothing but the trigger contexts, with the Trigger params and dataset present in that context. So in order to add your logic, You don’t need to write any trigger anymore. Just extend this class and override the necessary method, which you want to use.
For example, On Account, I want to have 2 before insert logics
Now, how will the system know, which method to run in which context and how will it run?
We need to give information to the system. Every time we create a class with some logic, we need to register it somewhere, so that, the system will read it and does the necessary action.
A custom metadata type, which will be used for the registry. (You can go for custom object too, but it will be counted against SOQL limits)
It has the following attributes:
- Class Name: Your class name(with namespace, if it has) having logic.
- Context : Picklist (Before/After)
- Object Name: In which object’s trigger, the logic should be triggered.
- Operation : Picklist (Insert/Update/delete/undelete)
- Is Active : to enable/disable logic, on-demand.
Let’s register our classes,
So, we are done with registration. Now let’s see how would the system use this registry.
Run Method in AbstractTriggerHandler — Entry Point Function
The Run Method will be responsible for calling all our registered classes.
- It will get the custom metadata records first using the object name and Trigger. Operation type.
2. Once it gets the custom metadata records, then for each record, it gets the instance of the class to be called using the concept of Reflection. Once the instance is available, it will call the context methods using the Trigger.operationType.
If the current context is BEFORE_INSERT, All classes having before insert registry will be instantiated and their beforeInsert() will be called one by one, then as soon as context changes, respective registries will be picked and respective methods will be called.
Now, Our AbstractTriggerHandler class is complete and it will look something like below:
Actual Trigger Changes: Just call the run method and pass all the data, and you are done.
Let’s test it.
- Create one account.
- Check debug logs
Possible Future Enhancements
- In Custom metadata, you can add validations for some valid combination of trigger context. For example, Before Undelete, or After delete.
- We can add one sequence field to run the contexts in sequence.
- We can create helper methods for each context. Since all logics of similar contexts have been clubbed, they can share resources now. For example, no need to query data every after insert, query in first and pass it to next.