Lightweight Apex Trigger Framework

Lightweight Apex Trigger Framework

Pssst… Just want to head straight to the trigger framework code? Get it on Github.

Trigger frameworks in Apex are a funny thing. We all know we need them, we all know that it is best practice to remove logic from the triggers themselves for several reasons (discussed below), and we all know what a pain it is to unit test code sitting directly in a trigger.

I needed to put a trigger framework in place for a new customer a couple of weeks ago. Being the creative, talented developer that I am, my first port of call was, of course, Google. Why recreate the wheel? There are loads of these frameworks around.

Here are three frameworks that I looked at:

The above frameworks are great, but for my needs they were overkill. Some of the features putting me off of these frameworks were:

  1. Having both a BulkBeforeInsert(List) and BeforeInsert(SObject) type method for each event. The first method fires with all records, and the second method fires for each method individually. I don’t really understand the point in the single record method. I quite often hand all of the records to some kind of “service” for processing if it’s an advanced operation. Also, this means that even if I don’t implement the single record method, my 200 trigger items are being iterated through twice on every trigger – once before, and once after.
  2. Complex trigger factory logic: A class which ties together a trigger and its handler. These are usually either implemented by writing some code with some IF statements in, or a more creative example I’ve seen uses a naming convention where the trigger must be called “OpportunityTrigger” and the handler “OpportunityTriggerHandler”. The factory can then automatically find the handler by name and uses it. Given that a trigger for a particular object will only ever call one trigger handler, and that handler will never change, I’m not sure what benefits either of these approaches provide.

  3. Generally complicated design. Hari Krishnan’s framework is fantastic, but if you take a look at his UML diagram you can see why I was put off of his pattern. There are undoubtedly advantages to using his architecture, but I wasn’t going to need those features. The biggest put off for me was having a separate handler class for each event. Again, for my implementation, this is overkill (and I’m not entirely sure what the advantage of this approach is).

I think accessibility with these frameworks is a real barrier for some developers. A lot of Salesforce developers I know are not from a strong Object Oriented Programming background, and can therefore feel a bit intimidated when they look at a framework which comprises of multiple base classes and interfaces, as well as using patterns such as factories and dependency injection which they may not be familiar with.

Time is another barrier. Working as a consultant, there is never enough time in the day. When the time comes to add that first trigger to an org, it takes real discipline to sit down and spend time designing a robust trigger strategy. It’s often much easier to just add the logic straight into the trigger, especially when it’s “just a few lines of code”. It’s not until those lines breed and multiply that the problems with this lack of planning become apparent.

With the above points in mind I decided to write a straightforward, lightweight trigger handler framework which could serve as a base for any orgs that I work on (and hopefully yours too!). We’ll discuss the approach and functionality below, but if you’re just here for the code then you can go and pick it up from Github.

Why should I use a trigger framework?

A trigger handler framework is a way to remove logic from your triggers and enforce consistency across the platform. The framework itself will do the heavy lifting for you in terms of figuring out which kind of trigger is currently running and firing the correct logic.

Here are some of the advantages of using a trigger framework:

  • Removing trigger logic from the trigger makes unit testing and maintenance much easier.
  • Standardising triggers means all of your triggers work in a consistent way.
  • A single trigger per object gives full control over order of execution.
  • Prevention of trigger recursion.
  • It makes it easy for large teams of developers to work across an org with lots of triggers. I recently worked on an org which had triggers on around fifty objects, and the fact that every trigger was implemented in a consistent manner made it easy to go in and make changes or add new triggers.

A trigger framework is a great idea if you have more than one developer working in your org. It enables the lead/architect to define how every trigger in the application should work. Using a framework allows the architect to make decisions such as “every trigger must implement a custom setting which allows us to switch the trigger off”. Or perhaps the architect wants to ensure that any validation which occurs in a trigger must be implemented by a method called “Validate()” on the trigger handler for consistency. I’ve worked on orgs where different developers have their own way of implementing triggers, and it makes debugging extremely difficult when one trigger has 1000 lines of code in it, doing 20 different tasks.

Requirements for my trigger framework

Now that I’ve moaned about everyone else’s frameworks, here’s my attempt at something a bit simpler.

  1. The framework should be easy to understand. Any developer should be able to see how the framework works without having to read through a load of boilerplate code and comments.
  2. A single handler per object to handle all events in bulkified form (no need for single record implementations).
  3. An interface for the trigger handlers to enforce consistency.
  4. No complex trigger factory logic, we’ll use some basic dependency injection instead.
  5. Need the ability to switch off triggers. This ability must be enforced on every trigger.
  6. Initial implementation of the framework on a new org needs to be extremely simple.
  7. Despite the framework being lightweight, it needs to be extensible so that it can be added to as the requirements of the org grow.
  8. Developers only need to worry about writing their trigger handler class. They shouldn’t need to have to change or even understand the underlying logic of the framework itself (no need to put a new IF statement in a TriggerHanderFactory class, for example).

The interface

The interface dictates which methods every trigger handler must implement, even if these methods have no code in them. By implementing the methods in this class, the TriggerDispatcher (discussed below) can be confident that the trigger handler has a method for each of these events:

  1. Before/After Insert
  2. Before/After Update
  3. Before/After Delete
  4. After Undelete
  5. IsDisabled

Here’s the code:

public interface ITriggerHandler 
{
	void BeforeInsert(List<SObject> newItems);

	void BeforeUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems);

	void BeforeDelete(Map<Id, SObject> oldItems);

	void AfterInsert(Map<Id, SObject> newItems);

	void AfterUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems);

	void AfterDelete(Map<Id, SObject> oldItems);

	void AfterUndelete(Map<Id, SObject> oldItems);

	Boolean IsDisabled();
}

If you’re unfamiliar with interfaces, don’t worry. You don’t need to do anything with this class for now. Just make sure it exists in your org.

The Dispatcher

The dispatcher is responsible for making sure all of the applicable methods on your trigger handler are called, depending on the current trigger context. It also contains a check to make sure that the trigger has not been disabled. If the trigger has been disabled (more on this below), then the trigger events will not be fired (See lines 10/11).

Again, this is part of the framework and you do not need to change this code in any way. Just make sure the class is in your org.

Here’s the code:

public class TriggerDispatcher 
{
	/*
		Call this method from your trigger, passing in an instance of a trigger handler which implements ITriggerHandler.
		This method will fire the appropriate methods on the handler depending on the trigger context.
	*/
	public static void Run(ITriggerHandler handler)
	{
		// Check to see if the trigger has been disabled. If it has, return
		if (handler.IsDisabled())
			return;
			
		// Detect the current trigger context and fire the relevant methods on the trigger handler:

		// Before trigger logic
		if (Trigger.IsBefore )
		{
			if (Trigger.IsInsert)
				handler.BeforeInsert(trigger.new);

			if (Trigger.IsUpdate)
				handler.BeforeUpdate(trigger.newMap, trigger.oldMap);

			if (Trigger.IsDelete)
				handler.BeforeDelete(trigger.oldMap);
		}
		
		// After trigger logic
		if (Trigger.IsAfter)
		{
			if (Trigger.IsInsert)
				handler.AfterInsert(Trigger.newMap);

			if (Trigger.IsUpdate)
				handler.AfterUpdate(trigger.newMap, trigger.oldMap);

			if (trigger.IsDelete)
				handler.AfterDelete(trigger.oldMap);

			if (trigger.isUndelete)
				handler.AfterUndelete(trigger.oldMap);
		}
	}
}

Creating a TriggerHandler

Let’s imagine we want to create a trigger for the Account object. For the sake of a very straightforward example, we’ll implement a trigger which rejects any new accounts which have the text “test” in their name.

First, we create a new class called AccountTriggerHandler. We can actually call this class anything we like, but let’s be consistent…

public class AccountTriggerHandler 
{

}

Next, we need to implement the ITriggerHandler interface. We need to add “Implements ITriggerHandler” to the end of the class declaration, and then we need to add all of the methods from the interface to the class, even if those methods do not contain any logic.

public class AccountTriggerHandler implements ITriggerHandler
{
	public Boolean IsDisabled()
	{
		return true;
	}

	public void BeforeInsert(List<SObject> newItems) {}

	public void BeforeUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}

	public void BeforeDelete(Map<Id, SObject> oldItems) {}

	public void AfterInsert(Map<Id, SObject> newItems) {}

	public void AfterUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}

	public void AfterDelete(Map<Id, SObject> oldItems) {}

	public void AfterUndelete(Map<Id, SObject> oldItems) {}
}

Notice that we had to fill in the IsDisabled() method. We’ll just return true for now, but ideally this could check a custom setting. I also like the ability to switch off triggers during unit tests so that I can control setup of the test data. For this, we’ll add a static property to the class and check this in the IsDisabled() method.

Let’s add our trigger logic:

public class AccountTriggerHandler implements ITriggerHandler
{
	// Allows unit tests (or other code) to disable this trigger for the transaction
	public static Boolean TriggerDisabled = false;

	/*
		Checks to see if the trigger has been disabled either by custom setting or by running code
	*/
	public Boolean IsDisabled()
	{
		if (TriggerSettings__c.AccountTriggerDisabled__c = true)
			return true;
		else
			return TriggerDisabled;
	}

	public void BeforeInsert(List<SObject> newItems) 
	{
		// Reject any Accounts which have the word "Test" in the name
		for (Account acc : (List<Account>)newItems)
		{
			if (acc.Name.contains('test'))
				acc.Name.addError('You may not use the word "test" in the account name');
		}
	}

	public void BeforeUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}

	public void BeforeDelete(Map<Id, SObject> oldItems) {}

	public void AfterInsert(Map<Id, SObject> newItems) {}

	public void AfterUpdate(Map<Id, SObject> newItems, Map<Id, SObject> oldItems) {}

	public void AfterDelete(Map<Id, SObject> oldItems) {}

	public void AfterUndelete(Map<Id, SObject> oldItems) {}
}

If I wanted to stop this trigger from firing while in a unit test, I could just add the following code to the test:

AccountTriggerHandler.TriggerDisabled = true;

Because I am enforcing this method at the interface level, I know that any trigger in my org should have the ability to be disabled either via custom setting or by setting a static property. Of course, a developer could just put “return false” in that method and not bother implementing the correct logic. I’ve found that punching these developers is often an effective solution, and sends an important message to the rest of the team. (Disclaimer: Don’t do that).

Hooking up the trigger

Now we just need to hook the trigger itself in. Create a trigger on your object and make sure it fires on all events.

We only need a single line of code to hook the trigger handler in via the dispatcher. See below.

trigger AccountTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) 
{
	TriggerDispatcher.Run(new AccountTriggerHandler());
}

We simply call the static method on the TriggerDispatcher, and pass it a new instance of our AccountTriggerHandler. The framework takes care of the rest.

This may have seemed like a bit of work, but going forward you would only need to create the triggerhandler and one line trigger for any future triggers.

ERD

Just in case you were wondering how this looks as an ERD. “Pretty straightforward” is the answer…

Trigger Framework ERD

Conclusion

This framework may not meet ALL of your org’s requirements, but it’s easy to install (one class and one interface), and will get you up and running with consistent trigger implementations very quickly.

If you’re considering a trigger framework but don’t have the time to implement a more complex solution (or perhaps do not understand the more complex solutions), then you should probably start with a framework like this and build on it yourself. You can always add new functionality as and when it is required.

If, on the other hand, you are NOT considering a trigger handler framework, you should reconsider!

Any questions or comments? Feel free to post them here or reach me on Twitter.

17 thoughts on “Lightweight Apex Trigger Framework

  1. mambotx@yahoo.com'Anthony Barrow

    Line 20: for (Account acc : (List)accountList)

    Should it read
    for (Account acc : (List)newItems)

    Notice I changed the list variable to “newItems”.

    Reply
    1. Chris Aldridge Post author

      Well spotted Anthony, thanks for picking that up.

      I slightly restructured the code from GitHub for that code snippet, and missed that the variable name had changed (the code was previously in a separate method with an accountList argument).

      I’ve updated the code snippet now.

      Congratulations, you passed the test!

      Reply
  2. tim@timbott.com'Tim Bott

    Hi Chris,

    This is pretty sweet and i like its simplicity. I’m running into an issue creating Opportunity child records with this framework. It appears I’m missing opportunity Ids; is that a result of casting from an SObject?

    For example, in the above Before Insert example, I think that “acc.Id” would return a null value. At least…that is the behavior occurring in my test class…

    any ideas?

    thanks,
    tim

    Reply
    1. Chris Aldridge Post author

      Hi Tim,

      No, that’s not a side effect
      Of casting to and from an SObject.

      Are you trying to retrieve the opportunity ID in a before insert trigger?

      If so, the records won’t have IDs because they haven’t been inserted yet.

      If you are trying to create a child record for a record in the before insert, it sounds like you just need to move your code into the after insert trigger section instead.

      In an after insert trigger, the records will have an ID.

      I may have misunderstood your issue there. If that’s the case, come back to me and I’m sure we can get your problem solved.

      Reply
  3. mailtoharshit@gmail.com'Harshit Pandey

    Neat work Chris,

    I wrote Weave sublime plugin to spin up scaffold code, I am working on adding more patterns on triggers. I like this and simplicity with your due permission, can I fork and modify it a little to make as generic and write a new snippet to be added in sublime plugin ?

    This is what I am talking about : http://www.oyecode.com/2016/01/weave-sublime-text-snippets-plugin-for.html
    Official Page : http://mailtoharshit.github.io/Weave/
    PackageControl Page : https://packagecontrol.io/packages/Weave

    Reply
  4. shiv.janaki@hotmail.com'Bennie

    Great one Chris. I was always looking for a simpler framework and this is what every Developer needs. Thanks much

    Reply
  5. telford.andrew@cba.com.au'Andrew

    Hi

    Can you tell me why in the triggerhandler you use List for BeforeInsert but Map on the rest?

    public void BeforeInsert(List newItems)
    public void BeforeUpdate(Map newItems, Map oldItems) {}

    Reply
  6. telford.andrew@cba.com.au'Andrew

    Sorry ,,,

    I am trying to use the framework but I am having issues getting the passed Map into a list or a format that I can then read and use. I am new to this and I have looked through a few of the help sites but not getting very far. this is what I have so far.

    public void AfterInsert( Map newItems )
    {
    //– Collect the common information
    //———————————————————————————————————————-
    SYSTEM.debug(‘DEBUG TASK: AfterInsert Task’);
    Map thisTask = new Map(newItems); //– Error here = Invalid initial type Map for Map
    Set contactIDs = new Set();
    List updateContacts = new List();
    FOR( Task tsk : thisTask )
    { contactIDs.add( tsk.WhoId ); }
    }

    Any assistance would be appreciated.

    Reply
      1. telford.andrew@cba.com.au'Andrew

        Sorry … I am still a little lost and my early attempt to post code appears to have been cleaned. Lets try that again.

        This is the code I currently have:
        public void AfterInsert( Map>Id, SObject< newItems )
        {
        //– Collect the common information
        //———————————————————————————————————————-
        List<Contact> contsToUpdate = new List<Contact>();
        List<Contact> updateContacts = new List<Contact>();
        Set<<D> contactIDs = new Set<ID>();
        List<Task> thisTask = new List<Task>();

        SYSTEM.debug('Where Are We?: AfterInsert Task');
        SYSTEM.debug('What type of object?: ' + newItems.getSObjectType() );
        SYSTEM.debug('What is our Size: ' + newItems.size() );
        SYSTEM.debug('Who?: ' + newItems.get('WhoId') );

        Map<Id, Task> MyMap = new Map<Id, Task>(newItems); //– Error here
        thisTask.addAll(MyMap.values());
        }

        This is the error I get when I save the TaskTriggerHandler class
        Invalid initial type Map<Id,SObject> for Map<Id,Task>

        Reply
  7. shiv.janaki@hotmail.com'Bennie

    TriggerSettings__c.AccountTriggerDisabled__c = true > this is how you call your custom setting? Strange

    Reply
  8. cropredy@gmail.com'cropredy

    Chris — I must admit I chuckled at your comment on the complexity of some trigger frameworks. The fflib__ classes that come with Andy Fawcett’s Force.com Enterprise Architecture are a good example (albeit his framework addresses many other issues as well and the domain layer is just one aspect of an overall well-thought-out pattern).

    Anyway, it would be worth your while to comment (perhaps in the main post) whether (and I think it does) your framework addresses two trigger recursion issues that sometimes trip up developers:

    (1) A transaction that does DML on more than 200 records (e.g. execute anonymous, VF controller, REST service, …) One doesn’t want the execution of records 201-… to be halted because of a recursion Boolean set during records 1-200

    (2) A transaction that uses Database.insert(someList,false) where the allOrNothing argument allows partial successes. Per the SFDC APEX doc, if any errors occur, the triggers will be re-executed a second and possibly a third time on the records that succeeded on pass 1. Again, one doesn’t want the trigger recursion Boolean to prevent execution of the handler on pass 2 (3) just because on pass 1 the recursion Boolean was enabled.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *