The “One Trigger per Object” design pattern - SDFC 99


What is the “One Trigger per Object” design pattern?
It’s exactly as it sounds! You combine all possible triggers on a specific object into just one trigger.

How do you combine many triggers into one?
Instead of coding your logic directly inside a trigger, you code it separately in a class. Then in your trigger, you create an object of the class, then run your records through its logic. Repeat for all triggers!

What are the benefits of this design pattern?

Reusability – with your logic in a class, you can now re-use it outside of triggers, for example in a Visualforce page, test class, batch Apex, etc. No need to copy code!
Simplicity – every “trigger” gets reduced to just two lines of code in the master trigger. An object oriented code base is more organized, predictable, and modular too!
Control your order of execution – in Apex, “the order of execution isn’t guaranteed for multiple triggers on the same object.” This pattern gives you total order control.
Code in style! You’re going to look like an Apex master using this pattern whether or not you take advantage of any of the benefits above. Plus, it’s a common interview question!
The “One Trigger per Object” template

trigger MasterOpportunityTrigger on Opportunity (
  before insert, after insert, 
  before update, after update, 
  before delete, after delete) {

  if (Trigger.isBefore) {
    if (Trigger.isInsert) {
      // Call class logic here!
    } 
    if (Trigger.isUpdate) {
      // Call class logic here!
    }
    if (Trigger.isDelete) {
      // Call class logic here!
    }
  }

  if (Trigger.IsAfter) {
    if (Trigger.isInsert) {
      // Call class logic here!
    } 
    if (Trigger.isUpdate) {
      // Call class logic here!
    }
    if (Trigger.isDelete) {
      // Call class logic here!
    }
  }
}

Here’s what’s happening in the template:

We create a master trigger that runs on every possible scenario (before update, after delete, etc).
Use trigger context variables to isolate a portion of your trigger to each possible scenario.
Use your classes in their appropriate trigger scenario. We’ll do an example below.
Example: Converting a trigger into a class

Now that we have our template, let’s take an existing trigger and convert it into a class.
We’ll use our trigger from Chapter 6 as an example:

// Check a checkbox only when an Opp is changed to Closed Won!
trigger Winning on Opportunity (before update) {
  for (Opportunity opp : Trigger.new) {
    Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
    Boolean oldOppIsWon = oldOpp.StageName.equals('Closed Won');
    Boolean newOppIsWon = opp.StageName.equals('Closed Won');
    if (!oldOppIsWon && newOppIsWon) {
      opp.I_am_Awesome__c = true;
    }
  }
}

Converted into a class:

public class WinningOppChecker {
  // These variables store Trigger.oldMap and Trigger.newMap
  Map<Id, Opportunity> oldOpps;
  Map<Id, Opportunity> newOpps;
  
  // This is the constructor
  // A map of the old and new records is expected as inputs
  public WinningOppChecker(
    Map<Id, Opportunity> oldTriggerOpps, 
    Map<Id, Opportunity> newTriggerOpps) {
      oldOpps = oldTriggerOpps;
      newOpps = newTriggerOpps;
  }
  
  // The one method your master trigger will call
  public void checkWinningOpps() {
    for (Opportunity newOpp : newOpps.values()) {
      Opportunity oldOpp  = oldOpps.get(newOpp.Id);
      Boolean oldOppIsWon = oldOpp.StageName.equals('Closed Won');
      Boolean newOppIsWon = newOpp.StageName.equals('Closed Won');
      if (!oldOppIsWon && newOppIsWon) {
        newOpp.I_am_Awesome__c = true;
      }
    }
  }
}

Note that you’re always going to pass in Trigger.new (or newMap) into your converted class’s constructor because your class needs to know which records to act on. In this case we also pass Trigger.oldMap since we’re comparing old and new values.

To keep the implementation simple, your trigger should only need to call one method of your class.

Final Implementation

trigger MasterOpportunityTrigger on Opportunity (
  before insert, after insert, 
  before update, after update, 
  before delete, after delete) {

  if (Trigger.isBefore) {
    if (Trigger.isInsert) { } 
    if (Trigger.isUpdate) {
      // This post's example implemented in our master trigger!
      WinningOppChecker checker = 
        new WinningOppChecker(Trigger.oldMap, Trigger.newMap);
      checker.checkWinningOpps();
      
      // Add other classes in your preferred execution order
      ClosedOppWelcomeEmailer emailer =
        new ClosedOppWelcomeEmailer(Trigger.new);
      emailer.sendWelcomeEmails();
    }
    if (Trigger.isDelete) { }
  }

  if (Trigger.IsAfter) {
    if (Trigger.isInsert) { } 
    if (Trigger.isUpdate) { }
    if (Trigger.isDelete) { }
  }
}

Comments

Popular posts from this blog

How to prepare your PC for the Windows 10 upgrade Source: WC

Top 5 Japanese Anime (Cartoons)

Salesforce LWC - Mass Approval Component