Nuances of Working with Tidy Trigger Pattern

Am working on a project in an org where I have to work with Dave Scott’s Tidy, Streamlined, Bulkified Trigger Pattern also known as the Trigger Factory pattern which can be found in the Salesforce Cookbook. There are nuances to it that can make it difficult to work with for some use cases.

For example, one can’t perform queries from within the Object Handler Class. That makes it difficult to use when one needs to query related records as they must be done in another helper class. Making those callouts appears to be something of a challenge as well. The “bulk before” and “bulk after” methods are iterators. As such, they’re controlled by the for loop iterators in the Execute method of the Trigger Factory base class as per the code example below:

private static void execute(ITrigger handler)
{
    // Before Trigger
    if (Trigger.isBefore)
    {
        // Call the bulk before to handle any caching of data and enable bulkification
        handler.bulkBefore();

        // "before" code section omitted

    }
    else
    {
        // Call the bulk after to handle any caching of data and enable bulkification
        handler.bulkAfter();

        // Iterate through the records deleted passing them to the handler.
        if (Trigger.isDelete)
        {
            for (SObject so : Trigger.old)
            {
                handler.afterDelete(so);
            }
        }
        // Iterate through the records inserted passing them to the handler.
        else if (Trigger.isInsert)
        {
            for (SObject so : Trigger.new)
            {
                handler.afterInsert(so);
            }
        }
        // Iterate through the records updated passing them to the handler.
        else if (Trigger.isUpdate)
        {
            for (SObject so : Trigger.old)
            {
                handler.afterUpdate(so, Trigger.newMap.get(so.Id));
            }
        }
    }

    // Perform any post processing
    handler.andFinally();
}

What the above means is that when you get to the point of writing code for the actual trigger handler, in an After Trigger, it begins to look something like this:

// note: public variables defined earlier
public void afterInsert(SObject so)
{        
    User u = (User)So;
    if(u.ProfileId == ProfileId && u.isActive == true){
        CommUsrId2CtcId3.put(u.Id,u.contactId);

            // can't make a callout here from a list 
            // not certain if I can make a callout here at all on a per record basis
            // appears I need to wait until the finally method to do DML or callouts
            // can't set flag on custom field in user object for tracking
            // can't set a boolean flag to prevent re-entry to this part here since
            // it would be in an iterative loop & repeat up to 200 times
    }        
}

public void afterUpdate(SObject oldSo, SObject newSo)
{

}

public void andFinally()
{
    if(Trigger.IsAfter && Trigger.IsInsert){ 
        ApexSharing_UserHandler(CommUsrId2CtcId3, true);

        // could do DML to custom object which causes @future trigger to fire
        // could do DML on User object to set flag on custom field for tracking
        // could set a boolean flag to prevent re-entry
        // appears I can use a for loop here, but not certain of that

    }    
}

I’m trying to refactor code to work with this pattern and am feeling hamstrung by it, but the client insists that it be used. I can respect that, but need to be able to call other handlers to do the work and need to figure out where and how to call them in an efficient manner. Have I drawn the correct conclusions in my commented code about what I can and cannot do using this pattern? Are there other limitations I need to be aware of?

Answer

The Tidy Trigger pattern requires one to think in a particular way

  1. Only one triggerhandler per SObject
  2. In the bulk before/after methods, collect up all related records that the rest of the trigger handler will need. For example, if the Trigger is on Opportunity, collect into a map all of the Opportunities’ Accounts and perhaps a separate map, all of the Opportunities’ OLI. I tend to do this in a separate OpportunityTriggerGateway class.
  3. In the before/after insert/update handlers, you process only a single record at a time but you can refer to the related records’ maps in #2. As you process each record, you can save interesting things in collection variables that are part of the triggerhandler object. For example, for those Oppos in the trigger set that are closed won, perhaps you want to note them in a map of oIdToContractMap where you build up a Contract record from the Opportunity. You’ll use this map in andFinally.
  4. In the andFinally (and typically, the execution of this after the after handlers, you will do bulk DML on related objects (in my example, insert Contracts). You can also launch async transactions. Order-dependent actions are handled here.

As @Giene noted, the handlers for the before/after insert/update/delete events typically invoke another class that does the actual work on an instance of the SObject (with an argument passed to the wherever you collected the related records in #1). These could be service-type classes or simply a wrapper on the SObject with utility methods.

Maintaining via a separate class a static collection of IDs that you don’t want to reprocess on any sort of trigger recursion isn’t represented in the pattern as documented but easy to add in.

When doing bulk DML in the andFinally, if you are supporting partial successes, you’ll need to correlate the error on the DML’d record(s) to the triggered item that led to that DML so you can put addError on the sole triggered record and not fail the whole set of up to 200.

Attribution
Source : Link , Question Author : crmprogdev , Answer Author : cropredy

Leave a Comment