Sunday, January 5, 2020

Curiosity Unlocked: Can You Guarantee the Order in Which Triggers Will Fire?

January 05, 2020 Posted by Pat Penaherrera 6 comments
"One Trigger to Rule Them All."  This has long been a best practice and fundamental design pattern on the Salesforce platform.  Instead of creating multiple Apex Triggers on a given object, you create only one, and instead hand off execution of your business logic to a Trigger Handler framework (or equivalent pattern) which will orchestrate the execution of said business logic.  There are many reasons why developers take these wise words to heart.  One specific reason for this is that we cannot know the order in which Apex Triggers will fire when there are two or more triggers implemented for the same trigger event on the same object.  Or can we?  The truth may surprise you!



The documentation is very clear on the matter.  Per the Apex Developer Guide, "the order of execution isn’t guaranteed when having multiple triggers for the same object due to the same event. For example, if you have two before insert triggers for Case, and a new Case record is inserted that fires the two triggers, the order in which these triggers fire isn’t guaranteed."  Let's test this statement, shall we?

First, Some Test Prep

First things first.  Before we experiment, make sure you do so in s a safe place, such as a scratch org, Trailhead playground, or a personal developer org.  I do not recommend testing within any client- or employer-owned orgs, for obvious reasons.  If you have not worked with scratch orgs before, check out this quick-start module on Trailhead.  And if you are new to Trailhead and Trailhead playgrounds, check out this trail which will walk you through getting started on this amazing (and free) learning platform.

Ready to go with a safe and fresh org?  Okay, let's do this.

Now for the Fun Stuff

You can run this test against any SObject, but I've used the Account object for the purposes of this demonstration.  Start by creating a simple trigger like the one shown below in your development environment of choice.  In my example below, I am creating a before insert/before update trigger.

trigger AccountTrigger1 on Account (before insert, before update) {
system.debug('Running AccountTrigger1...');
}

Create a few more triggers just like this one. I recommend numbering the triggers both in name and in the debug statements so that they can be easily identified later when we run our tests and view our logs.  For my testing, I created seven (7) triggers, e.g. AccountTrigger1, AccountTrigger2, ... AccountTrigger7.

Next, deploy the triggers to your fresh, clean test environment.  Navigate to Setup > Custom Code > Apex Triggers and you should see your triggers listed nicely on the page.  You should have something similar to the below.

Apex Triggers Menu in Setup

Open up the Developer Console and click on Debug > Open Execute Anonymous Window.  Since our triggers cover the insert and update events, creating or updating a record will suffice to trigger our tests.  Write a couple of lines to create a record for your SObject (the Account object, in this example), as shown below.  Check the option to Open Log and click Execute.


Filter the log to Debug Only and review the results.


Pretty interesting, huh?  It appears that our triggers ran in order!  We'll talk more about what that order may have been later.  But, perhaps this was a fluke.  Run another test using the Dev Console by inserting another record and generating another log.


Again, our triggers ran in the same order!  Two in a row, but perhaps still a fluke.  Run the test as many more times as you like.


Without a hitch, the triggers execute in the same order every time.  This is pretty amazing stuff considering that we've always been told that the order of execution in this scenario cannot be guaranteed.  We've been taught to expect (or at least, we've inferred) that the order would be random with every execution context.

What Does This Mean?

At this point, we can make a few educated guesses as to what may be happening here.  We can deduce that the platform may be using the Trigger Name, the Created Date, or the Last Modified Date/SystemModStamp to determine a fundamental order of execution.  We can't change the Created Date for any of these, so let's skip that for now.  Let's look at Last Modified Date/SystemModStamp and see if they are the culprit (as this is arguably the easiest to test of the three).  Modify one of your triggers with an arbitrary update like the one below, save, and deploy to your target environment.

trigger AccountTrigger3 on Account (before insert, before update) {
system.debug('Running AccountTrigger3... this one was modified most recently!');
}


In my example, I modified AccountTrigger3 and updated the debug statement so that it would pop in my logs.  If the Last Modified Date/SystemModStamp are what drives the execution order, then this trigger should jump to either the front or the back of the execution list when we run our next test (depending on the sort order used by the platform).

Now let's run another test and see what happens.


No change!  The order remains the same.  So, we know that the Last Modified Date/SystemModStamp is not the factor driving the execution order here.  What about the Trigger Name?  Could that be it?  Let's modify the name of one of our triggers and see what happens.

trigger AccountTriggerX on Account (before insert, before update) {
system.debug('Running AccountTriggerX... formerly known as AccountTrigger5!');
}

In my example, I changed the name of AccountTrigger5 to AccountTriggerX and updated the debug statement so that, like AccountTrigger3, it would pop in my logs.  If the server is using the Trigger Name to determine the order of execution, the results of our previous tests would suggest that the sort order being used is ascending order.  Therefore, I'd expect AccountTriggerX to drop to the bottom of the execution list, and be executed last when we run our next test.  Let's create another record and see what happens.


Amazing!  The order still hasn't changed.  AccountTriggerX is still showing as the 5th executed trigger in the list.  So we've ruled out two of the three obvious choices, with Created Date left on the list.  What happens if we create a new Apex Trigger and add it to the mix?

trigger AccountTrigger8 on Account (before insert, before update) {
system.debug('Running AccountTrigger8... this one was created after the original 7!');
}


In my example, I've introduced AccountTrigger8, created some time after the original 7 with which we started.  If Created Date is determining the execution order, then given that the first 7 triggers were deployed to the target org at the same time (and therefore have the same Created Date), I would expect this new trigger to be executed last for ascending execution order on Created Date (i.e. oldest triggers are run first), or first for descending execution order on Create Date (i.e. newest triggers are run first).  The moment of truth is upon us... let's create a record and see what happen.


[Editor's Note: At this point, Pat put on sunglasses so that he could take them off slowly, and dramatically.]

A Plausible Solution?

My word.  What it would appear we have here is a potential answer to our question.  It would appear that the Created Date is the key factor used by the platform to determine the execution order for our multi-trigger scenario.  Based on our findings, the oldest triggers appear to be executed first, and the newest executed last.  Let's add one more brand-new trigger to the mix, and then subsequently create a few more records to see what happens.


It holds up.  The newest trigger (now AccountTrigger9) was executed last, and the order of execution amongst our triggers remains consistent.  Could this be a mind-blowing discovery?  It certainly seems that way!

But Wait--- A Surprise Twist!

I decided to run one more test by again introducing a new trigger into the scenario.  This time, however, I gave the trigger another name outside of the convention I had be using.  I named the new trigger GammaTrigger, and ran a few more tests by creating records and observing the results.




Dang!  Our perfectly plausible solution has been debunked right before our eyes!  Notice how the GammaTrigger--- now the most recently created trigger of the bunch, and 10th overall--- has been executed in between the 7th and 8th triggers in our set.  This busts our theory that the Created Date is the driving force of our trigger execution order.

Wrapping Up What We've Learned

While our Created Date theory may have been debunked, it does not mean that our efforts were in vain.  Running a few more tests (i.e. firing our triggers again via the creation of new Account records) after the creation of the final GammaTrigger above confirmed that the order of execution for our triggers remained consistent.  Sure, we still don't know what the platform is using to determine the order of execution for our triggers in this scenario, but it does appear that it is using something consistently.  It seems that the order of execution for our triggers is reliably consistent until such time that you introduce another trigger into the scenario, at which point the order is adjusted and once again hardened until the next trigger is introduced (or removed).  

Our results suggest that you can guarantee the order of execution when having multiple triggers for the same object due to the same event.  That sounds like a success to me!

And with that said, sticking to one trigger per object is still by far the best advice on this topic. ;)

For the Exceptionally Curious

I did also test a few other scenarios which I did not cover in depth within this post in order to keep the post at a relatively reasonable length.  In case you were wondering what those were, below are a few bullets to summarize my additional findings.
  • Our results are consistent across other standard SObjects as well (I ran similar tests against the Lead object to ensure that this was not somehow a fluke isolated to the Account object).
  • The size of our triggers (without comments) does not appear to be a factor in determining the order of execution.  If you review our screenshots earlier in the post, you'll see that the order in which our triggers fire has no apparent correlation to the size of said triggers.
  • If you introduce a Workflow Field Update into the scenario (which then causes update triggers to run once more), debug logs show that the applicable triggers still run in the same order for their second invocation as well.
  • Running the tests with different trigger events produces the same results (although admittedly I did not test all trigger events).
  • Running the tests the next day (in order to account for any potential server-side caching effects) produces the same results.
  • Following my tests, I searched the web to see if anyone else had already stumbled upon similar findings, or perhaps if someone had already ventured further than I have and discovered the whole truth regarding what drives the order of execution for our scenario.  As of this publishing, I have not found any other articles or posts on this specific topic.

So what do you think of our findings?  Let me know your thoughts in the comments below, and feel free to share any findings if you consider running a few experiments of your own!  Excelsior!

6 comments:

  1. We can keep standard format for trigger name in order to execute them in a sequence :D

    ReplyDelete
    Replies
    1. Thanks for your comment, Ram! I did find in one of my tests that the sequence was not honored even when adding additional triggers with the same naming convention. However, in most cases, the naming convention kept the sequence in tact. Further testing is needed on this, but I agree with Dan below that, while certainly an intriguing find, I would not at all recommend relying on this pattern for anything other than exploring one's own curiosity. :)

      Delete
  2. You can't guarantee it, because Salesforce can change the behavior at any time without notice.

    ReplyDelete
    Replies
    1. I agree 100%, Dan! This was purely a pursuit of curiosity after I initially observed the behavior. The "One Trigger to Rule Them All" paradigm is by far the best course of action to ensure complete control over the execution of your business logic, among other benefits. I made mention to this at the end of my post as well, but I certainly could have done a better job of stressing that point. I'll treat this as a learning experience for my future posts and will make sure to be clear about best practices going forward. Thank you for your feedback!

      Delete
  3. Good one! I always wondered that one. This could be interesting when you have triggers added by a managed package in the mix.

    Did you do any testing with managed packages?

    ReplyDelete
    Replies
    1. Thanks Joaquin! No, I have not done any testing with triggers from a managed package. That may be an interesting follow-up to this post... I'll give it some thought!

      Delete