Deprecated: Optional parameter $list declared before required parameter $is_script is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php on line 21

Deprecated: Optional parameter $register declared before required parameter $footer_or_media is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php on line 45

Deprecated: Optional parameter $register declared before required parameter $footer_or_media is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php on line 104

Deprecated: Optional parameter $expire declared before required parameter $path is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_functions.php on line 54

Deprecated: Optional parameter $depth declared before required parameter $output is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/themes/entaro/inc/classes/megamenu.php on line 155

Deprecated: Optional parameter $depth declared before required parameter $output is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/themes/entaro/inc/classes/mobilemenu.php on line 147

Deprecated: Optional parameter $args declared before required parameter $wp_customize is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/extensions/customizer/extension_customizer.php on line 583

Deprecated: Optional parameter $args declared before required parameter $wp_customize is implicitly treated as a required parameter in /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/extensions/customizer/extension_customizer.php on line 606

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831

Warning: Cannot modify header information - headers already sent by (output started at /home1/oijoiv2f/public_html/wp-content/plugins/apus-framework/libs/redux/ReduxCore/inc/class.redux_cdn.php:21) in /home1/oijoiv2f/public_html/wp-includes/rest-api/class-wp-rest-server.php on line 1831
{"id":1024,"date":"2017-09-08T09:39:09","date_gmt":"2017-09-08T09:39:09","guid":{"rendered":"http:\/\/www.salesforcenextgen.com\/?p=1024"},"modified":"2020-12-28T19:37:24","modified_gmt":"2020-12-28T19:37:24","slug":"apex-trigger-framework","status":"publish","type":"post","link":"https:\/\/salesforcenextgen.com\/apex-trigger-framework\/","title":{"rendered":"Apex Trigger Best Practices and the Trigger Framework"},"content":{"rendered":"

Apex Trigger Best Practices and the Trigger Framework<\/h1>\n

Apex Trigger Best Practices<\/h3>\n

In this section we will understand the best practices a user needs to follow to ensure the Trigger runs smoothly throughout its lifetime.
\nWe have always run in the situation where we have begun with a trigger which has a few lines of codes and serves a single purpose. But as the time progress our trigger becomes more and more complex with addition of new business logic and keeps on growing. Soon it becomes difficult\/ impossible to manage.
\nSooner or later we are going to find our self in a situation where a trigger has become too difficult to maintain and even the slightest change may have a disastrous effect. Therefore I have compiled few best practices for writing Apex Trigger to avoid the above mentioned scenarios and they are mentioned below:<\/p>\n

1. One Trigger Per Object<\/h3>\n

The reason this is on top my best practice list is that, if we make more than one Trigger on an object then we are not able to control the order of execution of these trigger\u2019s i.e. there is no proper flow control. A single Trigger is capable enough of handling all the possible combinations of DML events and Trigger\u2019s context. Namely insert, update delete and undelete (Before and After). So as a best practice we just need to create a single Trigger and it will handle all of the context. For example see the below code:<\/p>\n

trigger OptyTrigr on Opportunity (
\nbefore delete, after insert, after update, after delete, after undelete, before insert, before update) {
\nWrite your Trigger logic here
\n}<\/p>\n

2. Logic-less Trigger<\/h3>\n

Next good practice is to make Trigger without covering any business logic inside the trigger itself, make it logic-less piece of code. All we want to do here is to invoke the Trigger on the desired DML event and let it delegate the task to a specific handler class. The main reason behind that is testing a Trigger is not easy if all the business logic is written in the body of the Trigger. If we have written methods inside the body of the trigger, then these methods cannot be exposed for testing purpose and methods of trigger cannot be used anywhere in the org. Therefore stuffing logic inside the body of the Trigger will make a mess one day in future. Therefore we should create a helper class which receives delegation from the Trigger and all the application logic is written there. Below is an example of the handler class.
\ntrigger OpportunityTrigger on Opportunity (after insert) \/\/Trigger
\n{
\nOpportunityTriggerHandler.HandleAfterInsert(Trigger.new);
\n}<\/p>\n

public class OpportunityTriggerHandler { \/\/ Handler Class
\npublic static void handleAfterInsert(List opps) {— Business Logic—-}
\n}<\/p>\n

3. Handler methods Specific to the Context<\/h3>\n

One more best practice is to create methods which are specific to the context, i.e. they are invoked based on the DML event. If I am handling more than one DML Event then I would write as many methods in the handler class not the Trigger and I would route the trigger flow to the correct Handler method based on the context variables like Trigger.isAfter or Trigger.isInsert etc.<\/p>\n

Now we come to the stage that we already know the best practice to create a Trigger but why we should use a Framework for the same. We are going to find answers to this question below.
\nWhy Use Framework?<\/p>\n

Trigger framework simplifies the development of a trigger, but it is not necessary to use framework all the time for the development of the trigger. Framework helps in the following manner:-<\/p>\n

1. Helps in following best practices
\n2. Implementation of new logic and new handler method in an easy manner.
\n3. Maintenance of code and testing of Trigger is simplified
\n4. Enforces consistent implementation of the trigger business logic
\n5. It Implements tool, utilities and abstraction and make the handler logic as lightweight as possible which will help in maintaining the code<\/p>\n

Let\u2019s discuss the usability of the framework in detail.<\/p>\n

Routing Abstraction<\/h3>\n

Routing abstraction is a logic in which the flow of the trigger transaction is transferred to a method and this transfer of control is based on the context of trigger. The logic will check in which context the trigger was invoked and it will dispatch the flow to the method responsible for handling the context. To implement this logic we have to make the logic to route the flow but if you use trigger framework then this routing would be handled by the framework.<\/p>\n

Recursion Detection and Prevention<\/h3>\n

Another common mistake a developer makes is letting the trigger run in a recursive manner. Recursion is a very powerful tool, but if appropriate care is not taken then it can result in unexpected outcome. Trigger framework, in this case, acts a watchdog and observes all the trigger execution and can detect recursion and figure out a way to handle the situation in an appropriate way.<\/p>\n

Centralize Enablement and disablement of Trigger<\/h3>\n

Turing on and off a trigger is not an easy task and the traditional method using change sets is time consuming. The ability to disable the trigger at will is a huge advantage and this can be achieved using trigger framework. Trigger framework can be connected to a custom setting in the org and it can control the on and off of the trigger.
\nLet\u2019s understand by following an Example of Framework provided in salesforce developer guide.
\nTrigger Framework example<\/p>\n

To begin with let\u2019s have a look at the basic Implementation of a Trigger on opportunity object.<\/p>\n

\/\/Trigger
\ntrigger OpportunityTrigger on Opportunity (after insert, after update, after delete, after undelete) {
\nnew OpportunityTriggerHandler().run();
\n}<\/p>\n

\/\/ The Handler Class
\npublic class OpportunityTriggerHandler extends TriggerHandler {<\/p>\n

public OpportunityTriggerHandler() {}<\/p>\n

}<\/p>\n

You can see that although there are more than on context that are being called in the trigger but we have only one line the trigger\u2019s actual body. The only line of code that we have written in the trigger\u2019s body is the one where we are calling the run method of the trigger handler class, i.e. \u201cnew OpportunityTriggerHandler().run();\u201d.<\/p>\n

Let\u2019s make this example more practical by adding a scenario and logic to it. But where will we write the logic, if you guessed Helper class, then you are right, we will make use of predefined context handler methods which are overridable. As context method are called automatically based on the DML Event we do not have to worry about calling them.<\/p>\n

Ok So let\u2019s assume that we have a requirement where we have to update Amount field to zero when an opportunity is marked Closed\/Lost. To implement this logic event we will use before update trigger. So the first thing which we will do is to override the existing before update method in the handler class and write our application logic there, and it will get called automatically by the framework. Write code as shown below.<\/p>\n

\/\/ The Handler Class
\npublic class OpportunityTriggerHandler extends TriggerHandler {<\/p>\n

public OpportunityTriggerHandler() {}
\nprotected void override beforeUpdate() {
\nsetLostOppsToZero();
\n}<\/p>\n

private void setLostOppsToZero(List) {
\nfor(Opportunity o : (List) Trigger.new) {
\nif(o.StageName == ‘Closed Lost’ && o.Amount > 0) {
\no.Amount = 0;
\n}
\n}
\n}
\n}<\/p>\n

Here you can see that not a single line of code was written inside the trigger. The only thing which we did was to override the method beforeUpdate in the handler class. But not only this, we had to do one more task and that was to cast the Trigger.new context variable into the list opportunity. We have to cast the context variable into the list of opportunity because trigger automatically cast these variables to the respective object automatically but when these variables are referred outside of the trigger, then we have to first cast them into the specific object type and then perform the required task, as shown above.<\/p>\n

So now we know that whenever there is a change in the requirement then we do not have to make any changes in the trigger body but instead modify the trigger handler class. We can have as many context hander methods as there are the DML Events.<\/p>\n

Below is the schematic of the Trigger Framework, it mentions what is offered by the framework and what developer has to provide to it.<\/p>\n

\"Salesforce<\/p>\n

Entity Diagram of Trigger Framework Image Source: https:\/\/code.google.com\/archive\/p\/apex-trigger-architecture-framework\/<\/p>\n

We have seen now how to implement the framework but how is the framework doing all this. We just created a Trigger handler class that inherit from a base class TriggerHandler provided by the framework. This class has the following roles.<\/p>\n

1. It has the definition of the overridable context methods for each event
\n2. It supervises and manages execution of the Trigger
\n3. It also provides the routing logic to call the right context method<\/p>\n

The run() Method<\/h3>\n

Run method is the method which provides access to the trigger framework. It initiates when it is called from inside the trigger. It is the method which runs the supervisory logic and is responsible for the appropriate routing of the trigger flow. It does all this by checking the current context and then calls the appropriate context method. Below is the source code of the run method.<\/p>\n

public void run() {<\/p>\n

if(!validateRun()) return;<\/p>\n

addToLoopCount();<\/p>\n

\/\/ dispatch to the correct handler method
\nif(Trigger.isBefore && Trigger.isInsert) {
\nthis.beforeInsert();
\n} else if(Trigger.isBefore && Trigger.isUpdate) {
\nthis.beforeUpdate();
\n} else if(Trigger.isBefore && Trigger.isDelete) {
\nthis.beforeDelete();
\n} else if(Trigger.isAfter && Trigger.isInsert) {
\nthis.afterInsert();
\n} else if(Trigger.isAfter && Trigger.isUpdate) {
\nthis.afterUpdate();
\n} else if(Trigger.isAfter && Trigger.isDelete) {
\nthis.afterDelete();
\n} else if(Trigger.isAfter && Trigger.isUndelete) {
\nthis.afterUndelete();
\n}<\/p>\n

}<\/p>\n

The methods called in the handler are defined logic-less, they are meant to be overridden if nothing overrides it then no action is taken and nothing really happens. They are defined as below:<\/p>\n

Protected virtual void afterInsert(){<\/p>\n

}<\/p>\n

Supervisor Logic<\/h3>\n

Trigger handler not only route the trigger execution but also performs other tasks as well, like preventing the recursion etc. Trigger framework provides a utility with which we can control the number of executions per trigger invocation. See the example below:<\/p>\n

public class OpportunityTriggerHandler extends TriggerHandler {<\/p>\n

public OpportunityTriggerHandler() {
\nthis.setMaxLoopCount(1);
\n}<\/p>\n

public override void afterUpdate() {
\nList opps = [SELECT Id FROM Opportunity WHERE Id IN\u00a0:Trigger.newMap.keySet()];
\nupdate opps; \/\/ this will throw after this update
\n}<\/p>\n

}<\/p>\n

Trigger framework also provides us with power to bypass invocation of a trigger. Although it is not a good practice to bypass a trigger but in a scenario where the creation of an object ends up creating another object, in this case a trigger may invoke when the second object is inserted. Below is an example of code where the trigger handler is bypassing the invocation of trigger and later on when the task is completed we can clear the bypass logic from the trigger, see the example code below.<\/p>\n

public class OpportunityTriggerHandler extends TriggerHandler {<\/p>\n

public override void afterUpdate() {
\nList opps = [SELECT Id, AccountId FROM Opportunity WHERE Id IN\u00a0:Trigger.newMap.keySet()];<\/p>\n

Account acc = [SELECT Id, Name FROM Account WHERE Id =\u00a0:opps.get(0).AccountId];<\/p>\n

Case c = new Case();
\nc.Subject = ‘My Bypassed Case’;<\/p>\n

TriggerHandler.bypass(‘CaseTriggerHandler’);<\/p>\n

insert c; \/\/ won’t invoke the CaseTriggerHandler<\/p>\n

TriggerHandler.clearBypass(‘CaseTriggerHandler’);<\/p>\n

c.Subject = ‘No More Bypass’;
\nupdate c; \/\/ will invoke the CaseTriggerHandler<\/p>\n

}<\/p>\n

}<\/p>\n

Finally on\/off switch ability to enable \/ disable trigger at the production environment. It has always been difficult to enable and disable the Trigger at production, when it is causing production issue. This is achieved by creating a custom setting in salesforce org and this functionality can be extended to Visualforce page and an admin can easily enable \/ disable a trigger at Production.<\/p>\n

Also check other trigger resource:<\/h3>\n
    \n
  1. Design Patterns for Bulkifying an Apex Trigger<\/a><\/li>\n
  2. Making the Apex trigger a bit more complex!!<\/a><\/li>\n
  3. Writing a Simple Apex Trigger !!<\/a><\/li>\n
  4. Your first Salesforce Apex Trigger from scratch!!!<\/a><\/li>\n
  5. Salesforce Interview Questions on Trigger<\/a><\/li>\n<\/ol>\n

    References:
    \nApex Trigger framework : https:\/\/github.com\/kevinohara80\/sfdc-trigger-framework\/blob\/master\/src\/classes\/TriggerHandler.cls<\/p>\n

    Entity Diagram of Trigger Framework : https:\/\/code.google.com\/archive\/p\/apex-trigger-architecture-framework\/<\/p>\n

    Trigger Pattern for Tidy, Streamlined, Bulkified Triggers : http:\/\/developer.force.com\/cookbook\/recipe\/trigger-pattern-for-tidy-streamlined-bulkified-triggers<\/p>\n","protected":false},"excerpt":{"rendered":"

    Apex Trigger Best Practices and the Trigger Framework Apex Trigger Best Practices In this section we will understand the best practices a user needs to follow to ensure the Trigger runs smoothly throughout its lifetime. We have always run in the situation where we have begun with a trigger which has a few lines of …
    Continue reading Apex Trigger Best Practices and the Trigger Framework<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[78],"tags":[40,79,61,74,45,75,38,83,42,82,55,91,92,51,52],"_links":{"self":[{"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/posts\/1024"}],"collection":[{"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/comments?post=1024"}],"version-history":[{"count":5,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/posts\/1024\/revisions"}],"predecessor-version":[{"id":2198,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/posts\/1024\/revisions\/2198"}],"wp:attachment":[{"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/media?parent=1024"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/categories?post=1024"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/salesforcenextgen.com\/wp-json\/wp\/v2\/tags?post=1024"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}