Chapter 2. Architecting Sustainable Triggers Using a Trigger Framework

This chapter starts with a quick refresher on Salesforce triggers. We'll look at the fundamental basics of how they work and when to use them. We'll then look at the concept of a trigger framework and dive deep into the how and why of a particular framework.

In this chapter, we will see the following topics:

  • An overview of triggers
  • Issues with traditional trigger architecture
  • Trigger frameworks
  • SFDC-trigger-framework

An overview of triggers

We are all familiar with the strange triangular hammer, which our doctor hits us with in the knee, making our leg kick out involuntarily. The key to reflexes is that they are automatic and programmed deep inside us. So deep, in fact, that at runtime (err, during life), it's a really big deal when our reflexes stop working. Likewise, Salesforce triggers work on a deep system level. Unlike human reflexes, however, we can program Salesforce triggers. Developers get to decide what happens when the reflexes are triggered.

Context is king

Triggers run in a well-defined but developer chosen context. We select the object, the Data manipulation language (DML) statement to act on, and even the before and after status of the DML call. DML refers to a set of keywords: insert, update, upsert, delete, and undelete. Triggers are, therefore, one of the most powerful tools in an architect's toolbox. Triggers are phenomenally flexible but with that phenomenal power comes the potential for phenomenal complexity. Because of this, triggers are not without an issue. In fact, their inherent flexibility is their greatest weakness. Salesforce allows you to create multiple triggers on the same object, with the same DML event and the same timeframe. However, Salesforce does not guarantee that your triggers will run in a deterministic order. Because of this, many older orgs with evolutionary changes made over years often run into issues with multiple triggers firing on the same object but without deterministic results.

When discussing triggers, first we need to determine the context in which the trigger will run. The context is made up of three crucial elements:

  • The object on which we wish the trigger to fire.
  • The DML statement which the trigger should listen for.
  • The timeframe in which the trigger will run. Essentially there are two options for this: before and after.

These three bits of information combine to form the trigger context. This information can be composed into a sentence that reads something like account after insert. In this case, account is the object the trigger will act on, insert is the DML statement, and after is our timeframe. Together, this context means our trigger will run after an account is inserted. Context is important because it tells us and the system when the events or trigger will happen. There are many contexts available. We can fire our trigger whenever we insert records, update records, merge records together, undelete records, or upsert records. Combine that with the two time frames before and after, and you end up with a whole host of trigger contexts that are available to us. When I talk about before or after, I'm referring to when the trigger will run. Will the trigger run before the data is inserted? after the insert? before the upsert? or after the delete? Timeframe is almost as important as the DML statement selection since certain features of the trigger framework and data are available to us before or after the DML statement. For instance, we won't have an ID for the record until after we've inserted it. Thus, we can't build logic in our triggers that relies on the records having an ID unless it's an after insert statement, or after the upsert trigger.

Trigger variables

Salesforce provides us with sets of trigger variables. These trigger variables are not available during all contexts. The first set of trigger variables are accessed via Trigger.new and Trigger.old. Both of these variables are lists containing each of the records that are being passed to the trigger for processing. These are typed, so if we fire trigger on our account, we will be given a list of accounts to process. As you might imagine, Trigger.old contains the values of the records prior to the trigger variable of the DML statement that fired this trigger. The Trigger.new variable represents what was just saved or what will be saved depending on the time frame of your trigger. For instance, in a before update trigger, the trigger.new variable contains the updated values of the records being updated. Conversely, trigger.old contains the original, unupdated values of the records being updated. Our second set of trigger variables is trigger.newmap and trigger.oldmap. These are maps with the ID as the key in the record as the Valley. It's important to note that these trigger context variables are only available during certain contexts. For instance, you will not have a trigger.old variable during a before insert trigger. Additionally, you will not have access to newmap or oldmap containing Id until after insert. While this seems logically intuitive, it can often trip up developers who are used to accessing records by ID from amount or by ID for certain logic behaviors.

An example trigger

Let's start off our work with an example trigger that is relatively straightforward. We will be working on a trigger that updates a related record via its lookup. In this situation, we will be updating a case whenever the contact is updated. You can imagine this trigger being useful whenever we have a call-center-type situation and we want to update the case to reflect the contact's current phone number whenever the contact's information is updated. However, before we start writing the code for this trigger, let's think through the context of this trigger.

First, we should decide which DML statement will fire our trigger. For this example, we will be firing our trigger on a DML statement of update. Why update? Because we'll likely already have a contact record associated with this case. Whenever the contact record changes, we will update the case. We want to fire our trigger on contact because that's the object we want the trigger to reflexively act on. With those decisions behind us, we know two-thirds of our context. Then, the only question left is, do we want to do this before or after the record is saved? At the end of a before trigger, the records are automatically saved. On the other hand, after triggers do not automatically save the records. Because the before triggers finish by saving the record, it's best practice to write your triggers as before triggers whenever possible. However, when updating an object that is different than the one the trigger fired on, an after trigger may make more sense. For instance, our example trigger fires when a contact is updated, but makes changes to a case record. Because of this, we likely want an after-update context for our example trigger. This ensures that we'll have the latest information in our contact record when we start changing our case record. Keeping little details, such as when the data is automatically persisted, in mind can be the hardest part of writing triggers. However, evaluating the proper context, especially the timeframe aspect, is the the most crucial part to triggers.

For our example trigger, we want to update the case record whenever the contact record is updated. This allows us to ensure that the most current phone number is available to the case owner, for example. However, any changes to the case record have the potential of firing other triggers or chains of triggers that potentially update the contact object again. This is called a recursive, cyclical, or re-entrant trigger. Let's look at the following figure as an example:

An example trigger

We can prevent most cyclical triggers using a static class variable as a semaphore. Simply create a simple class containing a public static Boolean variable hasExecuted, statement as follows:

Public static class triggerExecutionSemaphore {
Public static Boolean hasExecuted = false;
} 

Then, in your trigger, check for the status of hasExecuted like this:

if(TriggerExecutionSemaphore.hasExecuted == false) {
  //continue with trigger logic
  triggerExecutionSemaphore = true;
}

As you might imagine, this method of preventing cyclical triggers can become cumbersome especially, as the number of triggers on the same object increases.

Note

This is something you wouldn't necessarily need to use a trigger for; however, it is a good example for the use of trigger. Later in this chapter, we will get more into more detail on when and why you should use a trigger.

Let's try the following code snippet as another example of a trigger:

Trigger UpdateContactPhoneNumberOnCase on Contact (after update) {
  List<Case> affectedCases = [SELECT id, mainContactPhone__c 
          FROM Case 
          WHERE contactId = :trigger.new[0].id];

  if(trigger.old[0].phone != trigger.new[0].phone) {
    for(Case thisCase: affectedCases) {
       thisCase.mainContactPhone__c = trigger.new[0].phone;
       update thisCase;
    }
  }
}

In this version of the trigger, we define our context on the first line. We name it UpdateContactPhoneNumberOnCase and specify that it should fire after contacts are updated. The first thing this trigger does, after defining the context, is make a SOQL query for cases whose contact ID matches contacts that were just updated. Finally, our trigger loops through the affected cases; if the new phone number is different than the old, we change the case with the new phone number and update it. In this trigger, we simply take the first record passed in from the trigger context variable Trigger.new context, update the related cases using our trigger logic, and then update the case record. Conceptually, this is exactly what a trigger is meant to do. It reflexively affects other records whenever our contact record changes. While this trigger functions, it has several issues, most notably that it only updates cases where the contact is the first record in the incoming Trigger.new list context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.72.245