Summer’18: Rethink Trigger Logic with Apex Switch

With 17,110 points on the IdeaExchange idea, the arrival of the switch statement in Apex in the Summer’18 release is an exciting addition to the language that should please many developers.

About switch

As a language construct, switch comes in a number of forms in many languages. You might have seen a similar logical construct referred to as case, select, or some other things. The mouthful of a technical definition of switch is a multiway branch logical control flow feature. If you don’t want to spend time parsing that, it’s basically a syntax structure to evaluate a variable against many possible values without having a bunch of repetitive else if blocks. It can reduce repetitive code and improve clarity and readability of logical code paths.

Switch in Apex

Each implementation of this feature in each language needs to work for that language. Apex is no different — key point here being Apex is not a general purpose programming language. It is a Salesforce domain-specific language solving problems for developers working with the Lightning Platform.

One minor obstacle is that Apex greatly resembles Java, which uses the syntax switch...case. Because of this resemblance, many developers expect Apex to follow Java syntax and conventions. Only trouble is in Apex the word “case” already represents the class for the Case sObject. Even if the compiler could be made to cope with a keyword of “case” alongside a class called Case, it isn’t going to help the cause of readability or clear clean code. So although Apex resembles Java in many ways, the when keyword is preferred (Largely based on feedback from early beta testers in the Trailblazer community group). Here is a basic representation of the syntax with a String variable being compared to String literals:

switch on someStringVar {
  when 'value 1' {
    //...do something...
  }  
  when 'value 2', 'value 3' {
    //...do some other thing
  }
  when 'value 4' {
    //...do this thing...
  }
  when else {
    //...under all other circumstances
  }
}

Those of you who have used a version of switch statement in the C-family of languages will immediately notice the absence of a break statement in each when block.

If you really did take notice, good job!

That’s because in Apex there is no fall-through. This means the first logical branch is executed, then no others. In other words, the first executed branch blocks any following branches in the switch block.

The last Apex-specific feature I’ll mention is the types that you are allowed to switch on. Unremarkably, String, Integer and Long can all be compared to a literal value. But there are also sObject identifier and enum comparisons. These bring some new prospects to how a developer might now structure their code, especially in triggers.

Switch on sObject type

Allowing to evaluate when based on sObject type is exciting. Let’s consider a result set from a custom object, where we have queried the Owner field. Owner is polymorphic in a custom object, meaning it can be one of two types, either of type User or Group. If the owner is a User, we will send an email to that owner and if a Group, perhaps we will post to Chatter.

List<String> notifyUsers = new List<String>();
List<String> notifyGroups = new List<String>();

List<Property__c> properties = [SELECT 
                                    TYPEOF Owner 
                                        WHEN User THEN Id, Name, Username, Email 
                                        WHEN Group THEN Id, DeveloperName, Email, DoesSendEmailToMembers 
                                    END 
                                FROM Property__c
                                WHERE ID in Trigger.new];

for (Property__c prop: properties) {
    switch on (SObject) prop.Owner {
        when User u {
            notifyUsers.add(u.Email);
        }
        when Group g {
            notifyGroups.add(g.DeveloperName);
        }
    }
}

MyHelper.sendEmails(notifyUsers);
MyHelper.postToChatter(notifyGroups);

Note the requirement to upcast prop.Owner to sObject.. The reason for this is that Apex sees the Owner field as the current specific type of User or Group. If what you are already being given is the generic SObject type, this would not be necessary.

The Trigger.operationType context variable

Before I talk about switch and enums, we need to introduce a new trigger context variable. For a long time, there were six boolean methods on the Trigger class for testing the operation context of your trigger (before/after and insert/update/delete/undelete). These were adequate, but if you wanted to pass the information of the state of this execution, you needed to potentially have a way of passing them all around. This could mean passing around the Trigger class itself or creating your own structure to do so. Either way, there are downsides.

Well, now we’ve done that for you. All seven execution contexts have been wrapped into a single enum typed context variable called Trigger.operationType . This context variable is of the type System.TriggerOperation which has the following values:

  • BEFORE_INSERT
  • AFTER_INSERT
  • BEFORE_UPDATE
  • AFTER_UPDATE
  • BEFORE_DELETE
  • AFTER_DELETE
  • AFTER_UNDELETE

If you combine this new context variable and the new Apex switch feature, trigger code becomes much nicer to read and to reason about.

Switch and enum in triggers

Let’s say you have a trigger, and you’ve followed the best practice of one-trigger-per-SObject. You need to implement the following features in your trigger:

  • Create related records in the after update and after insert contexts.
  • On before insert set some values before creating the record.
  • For compliance, you need a sanity check to prevent deletion of certain sensitive data in a before delete context.

First let’s take a stab at this before the newTrigger.operationType context variable:

trigger BrokerTrigger on Broker__c (before insert, after insert, before update, after update, before delete, after delete, after undelete) {

    if (Trigger.isAfter()) {
    
        if (Trigger.isInsert() || Trigger.isUpdate()) {
            //create related records
        
        } else if (Trigger.isDelete()){
            //prevent deletion of sensitive data
        
        }
    
    } else {
        //this is my before context, not that you'd know necessarily
        
        if (Trigger.isInsert()) {
            //set value on record create
        
        }   
    }
}

This is working logic, but it also takes some effort to reason out what the trigger is doing. Never mind, if I want to pass this all to a helper method, there is no easy way to pass all of the possible context options.


Now let’s see this in action with the new stuff! Let’s even follow best practices and have a trigger handler static method that we call. So to start, we have the trigger itself.

trigger BrokerTrigger on Broker__c (before insert, after insert, before update, after update, before delete, after delete, after undelete) {

        BrokerTriggerHandler.handleTrigger(Trigger.new, Trigger.old, Trigger.operationType);

}

In this bit of code we’re invoking the handler method handleTrigger and passing context variables, including the new Trigger.operationType variable. The handler method now knows everything it needs to know about the current trigger context. Let’s look at the structure of that method:

public with sharing class BrokerTriggerHandler { 

    public static void handleTrigger(List<Broker__C> workingRecords, List<Broker__c> oldRecords, System.TriggerOperation triggerEvent ) {
    
        switch on triggerEvent {
        
            when AFTER_INSERT, AFTER_UPDATE {
                //create related records
            }
            when BEFORE_INSERT {
                //set value on record create
            }
            when AFTER_DELETE {
                //prevent deletion of sensitive data
            }
            when else {
                //do nothing for AFTER_UNDELETE, BEFORE_DELETE, or BEFORE_UPDATE
            }
        }
    }
}

If you ask me, this reads much more cleanly and understandably. I can reason out exactly what bits of code are being executed in each context and understand where the lines of logic go much more easily.

Awesome, but still version 1

Here it’s worth being specific on what you cannot do with switch in its very first implementation in Apex. In some languages, switch will allow you to compare to an expression, a variable, or perform pattern matching, either against contents of data structures or against a regex. If I allow myself to muse and dream of what could be amazing in the Apex, imagine all picklist value API names being automatically surfaced as enums in Apex and being able to switch on that. Not only would it make for super nice to write/read code, but it would also create a compile-time check for a valid comparison value!

 

Can you think of an amazing feature that would make switch even better? Please head over to IdeaExchange and submit that idea and socialize it.

So many more Apex features in Summer’18

This blog post calls out some of the features that caught my eye about switch in Apex, but be sure to take a look at the full documentation and release notes. Also, for better context about why it was built the way it was, there is a great video from TrailheaDX’18. And of course, this can work in many other places besides triggers.

There are a load of features coming to Apex in Summer’18, so be sure to read all the release notes and check out the recording of the Developer Preview in the Release Readiness Live webinar. And don’t forget your Summer’18 release highlights badge!

Finally, to go back to the basics, check out the Apex Triggers module on Trailhead.

Published
May 30, 2018

Leave your comments...

Summer’18: Rethink Trigger Logic with Apex Switch