[SalesForce] Difficulties with Appleman’s Trigger Classes

I'm working on a major project that has numerous triggers creating new records on 4 objects while also syncing updates back and forth between 3 of the 4 objects. The new records are primarily created when a trigger fires on only 1 object, but there's a total of 6 objects involved with a lot of pieces of data moving around between the objects.

This seemed like the ideal project to implement Appleman's Trigger Class Architecture. Unfortunately, I'm having a great deal of difficulty making it all work together. Apparently I don't fully understand how to direct flow between classes either prior or following "in process" DML operations; as in ones that happen while the trigger is executing, but not completely finished and has additional work to do.

I'm going to attempt to post some code below of class or two to illustrate what I'm working with for discussion.

First, here's the very simple trigger code:

trigger InsrtSyncCE_Event_On_Opp on Opportunity (after insert, after update) {

   TriggerDispatchMain1.Entry1('Opportunity',trigger.IsBefore, trigger.IsDelete, trigger.IsAfter, trigger.IsInsert, trigger.IsUpdate, trigger.IsExecuting, trigger.new, trigger.newmap, trigger.old, trigger.oldmap);
}

Next, here's the Main "Trigger Class" that "Dispatches" the work to other classes:

public class TriggerDispatchMain1 {

/* This class dispatches triggers to other classes for execution */

public static list<Opportunity> opstoupdate = new list<Opportunity>();
public static list<Event> evtstoupdate = new list<Event>();
public static Boolean InClass1;

public interface ITriggerEntry
{
    void MainEntry(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> newlist, map<ID, SObject> newmap, list<SObject> oldlist, map<ID,SObject> oldmap);
    void InProgressEntry(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> newlist, map<ID, SObject> newmap, List<SObject> oldlist, map<ID,SObject> oldmap );
}

public static ITriggerEntry activefunction = null;

public static void Entry1(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, List<SObject> newlist, Map<ID, SObject> newmap, List<SObject> oldlist, Map<ID,SObject> oldmap)
{   
    if(activefunction != null) 
    {
        activefunction.InProgressEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting, newlist, newmap, oldlist, oldmap );
        return;
    }

    if(TriggerObject == 'Opportunity') {

        if(IsAfter==true){ 

            if(ConditionA){ 

                activefunction = new TriggerMain1Class1();
                activefunction.MainEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting,  newlist, newmap, oldlist, oldmap);

            /* The above class creates a new Event (DML) for each Opportunity record  */ 
            /* then resets the Opportunity field that signalled condition A (DML) */ 

            }else{

                activefunction = new TriggerMain1Class2();
                activefunction.MainEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting,  newlist, newmap, oldlist, oldmap);

            /* Above class updates the Event when the Opportunity Owner changes */                      
            }
        }// end if(IsAfter == true
    }// end if(TriggerObject == 'Opportunity' 

    if(TriggerObject == 'Event') {

        if(IsAfter==true){

            list<Event>evtnewlist = (list<Event>)newlist;
            map<Id,Event>evtnewmap, etc;

            if('Create Assignment' condition){
                activefunction = new TriggerMain1Class3();
                activefunction.MainEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting,  newlist, newmap, oldlist, oldmap);
                /* this class requires Event to be updated after Assignment is created (DMLx2) */
            }
        }else{
        /* sync Event with assignment */

            activefunction = new TriggerMain1Class4();
            activefunction.MainEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting,  newlist, newmap, oldlist, oldmap);
       }
    } // End if(TriggerObject == 'Event'

    if(TriggerObject == 'Assignments__c'){ 
     // more of the same
    } // End if(TriggerObject == 'Assignments__c'
}
}

Next, here's the structure for the actual classes that contain the working code normally contained in a trigger:

public class TriggerMain1Class1 implements TriggerDispatchMain1.ITriggerEntry {

/* This class creates Events from Opportunities */

public void MainEntry(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, List<SObject> newlist, Map<ID, SObject> newmap, List<SObject> oldlist, Map<ID,SObject> oldmap)
{
    DiagnosticsInstrumentation.Push('DiagnosticsTriggers1.MainEntry');
    AfterUpdateOpportunityInsertEvent((List<Opportunity>)newlist, (List<Opportunity>)oldlist, (Map<ID,Opportunity>) newmap, (Map<ID,Opportunity>) oldmap, IsAfter, IsInsert, IsUpdate);
    DiagnosticsInstrumentation.Pop();
}

public static void AfterUpdateOpportunityInsertEvent(List<Opportunity> newlist, List<Opportunity>oldlist, Map<ID, Opportunity> newmap, Map<ID, Opportunity> oldmap, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate)
{
        list<Opportunity> oppnewlist = (list<Opportunity>)newlist;
    list<Opportunity> oppoldlist = (list<Opportunity>)oldlist;
    map<Id, Opportunity> oppnewmap = (map<Id,Opportunity>)newmap;
    map<Id, Opportunity> oppoldmap = (map<Id,Opportunity>)oldmap;

     /* code to sort the trigger.new vs trigger.old */

      /* code to create the Event(s) */

     /* code to get/store Id's of Opps that need to have field reset */

     /* DML operation to insert the Events(s) */

     /* DML operation to update the Opportunities */

    } // End of Inner Class


public void InProgressEntry(String TriggerObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, List<SObject> newlist, Map<ID, SObject> newmap, List<SObject> oldlist, Map<ID,SObject> oldmap)
{
     /* It appears that code can be redirected here provided there's no DML */
     /* in the prior inner class. Otherwise, the DML seems to trigger a redirect */
     /* back to the TriggerDispatchMain1 Class preventing this code from executing */

    if(TriggerObject == 'Event' && IsAfter)
    {
        TriggerDispatchMain1.activefunction = new triggerMain1Class3();
        TriggerDispatchMain1.activefunction.MainEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting, newlist, newmap, oldlist, oldmap);
        TriggerDispatchMain1.activefunction = this; 
    }

}//End inner class

} // End Outer Class

In essence, I don't seem to be able to control where I'd like the code to go or prevent multiple instances of triggers from firing due to the extra DML operations (or workflow and validation kicking in each time for that matter). As an example, I'd ideally like to be able to send the code when fired on Opportunity to create a new Event, directly into creating a new Assignment__c after it's finished without going through the trigger that fires on Event. The original Execution context of the 1st trigger that fired on Opportunity is still in effect when that happens.

I also want to use public static boolean InClass1 2, 3, etc variables to prevent recursion in the sync portions of the code, yet I'm not entirely certain how well that will work with this architecture since it operates on "instances" and the static variables won't act like instance variables that stay within the context of the same instance of a class. Instead, from what I've read and am seeing in testing, they'll act more like "Global" variables and won't expire when the instance does. If I understand things correctly (big "if"), if I put static variables in the sub class when it's called, it appears I lose the ability to prevent a new instance of a trigger from entering the sub class from the Dispatcher class.

So, any of you with good knowledge of constructors and classes, I'd greatly appreciate your assistance right now. With not having a Java background, this is an area that I'm not very strong in and still trying very hard to understand.

Edit

First, I should mention that I accidently omitted a crucial part of TriggerMain1Class1; the inner class called public void MainEntry and had gone directly to the code that's inside another inner class called public static void AfterUpdateOpportunityInsertEvent. My apologies for that. I hope the code makes much more sense now that those omissions have been included.

I need to add that one of the things I'd really like help with is understanding how to create another class or constructor of some sort within the existing ones that would allow me to redirect DML operations either back to the Dispatcher class or to another class that's dedicated to DML operations. I'm thinking that might help control flow by consolidating DML operations and preventing additional instances of triggers firing when "cleanup" DML operation are done after the primary task of the original code has completed (resetting values in Opportunity or Event fields that were used to tell the code to create new Events or Assignments__c's, etc).

The fact that the TriggerMainClass1 implements TriggerDispatchMain1.ITriggerEntry is something I don't quite know how to work around (or with) in terms of creating additional constructors other than the method used in Appleman's book using the code inside the public void InProgressEntry I've shown. Can I for example use the same code from within the public static void AfterUpdateOpportunityInsertEvent inner class of TriggerMain1Class1?

It makes sense that I could do that, however, I believe it works (please correct me if I'm mistaken) by sending things back through the Dispatcher class and then to the class that's being called. If I'm sending it back there, it would seem to me that I may as well use that class to do the DML by copying the updates into a static variable. Doing that would bring up the issues I raised earlier about handling static variables. Otherwise, a constructor carrying the objects to update in a map (or simply the Id's in a set) would be helpful; another issue I'm not confident on how to handle. I'd want a second constructor that has an additional method with the map containing the Ids and the object to be updated. Would I simply need to duplicate the ITrigger Constructor and add the map to the end of it when creating the constructor?

Does anyone have any thoughts or suggestions on how to implement the concepts I'm speaking of within this framework/architecture? I really need to get this working ASAP. I've been going in circles for well over a week now (going on two) and need to move things forward to make some real progress.

Update

I've done some additional work trying to create additional implementation constructors with limited success and need some help fine-tuning them to make them work. I know that a class can implement more than one method which is the approach I'm trying to set up. What I want to do is configure a class to be able to return DML for processing either to the Dispatcher class or another separate DML class which should cause the instance of the current trigger to expire. Here's some sample code of what I've tried or am attempting. One thing which is of concern is the static variable activefunction not applying to any of the other interfaces. I don't know how to work around that except to create additional static variables.

Here's snippets of the additional code:

public class TriggerDispatchMain1 {

public interface PDmlOpsEntry
{
    void MainEntry(String sObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> toupdate);
    void InProgressEntry(String sObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> toupdate);
}

/* note that other than the booleans, I've removed all the trigger contexts, */
/* only leaving the "toupdate" list for direct DML processing */
/* The booleans were important to keep in case the DML happened to be */ 
/* an insert or delete and not just an update */ 
/* Since I expect these to primarily be "returned", having an InProgressEntry */
/* context seemed like an essential thing to have */

public static PDmlOpsEntry activefunction1 = null;

public interface ITriggerEntry1
{
    void MainEntry(String sObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> newlist, map<ID, SObject> newmap, list<SObject> oldlist, map<ID,SObject> oldmap, list<SObject> toupdate);
    void InProgressEntry(String sObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> newlist, map<ID, SObject> newmap, List<SObject> oldlist, map<ID,SObject> oldmap, list<SObject> toupdate);
}
/* I also attempted adding a 2nd ITriggerEntry, but it didn't seem to work out well */
/* perhaps the problem was in the implementation by not using the "this" keyword? */

public static void Entry2(String sObject, Boolean IsBefore, Boolean IsDelete, Boolean IsAfter, Boolean IsInsert, Boolean IsUpdate, Boolean IsExecuting, list<SObject> toupdate)
{

    if(activefunction == true && activefunction1 != true) 
    {
        activefunction1.InProgressEntry(String, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting, newlist, newmap, oldlist, oldmap );
        return;
    }
    /* DML ops must enter as activefunction and initiate an activefunction1 */

        // Determine 
}

Here's how I've attempted to put the above into practice with the dependent classes:

public class TriggerMain1Class1 implements TriggerDispatchMain1.ITriggerEntry,  TriggerDispatchMain1.ITriggerEntry1, TriggerDispatchMain1.PDmlOpsEntry {

/* each TriggerMain1Class# has then needed an additional entry constructor for each */
/* interface and an additional method for each one before it could be compiled, */
/* assuming I was able to compile it at all */

I was hoping to be able to call something like the following from the dependent classes:

toupdate = (list<Opportunity>)oppstoupdate;
TriggerDispatchMain1.activefunction = new triggerDispatchMain1.PDmlOpsEntry();
        TriggerDispatchMain1.activefunction.PDmlOpsEntry(TriggerObject, IsBefore, IsDelete, IsAfter, IsInsert, IsUpdate, IsExecuting, newlist, newmap, oldlist, oldmap, toupdate);
        TriggerDispatchMain1.activefunction = ('Opportunity', ToUpdate==true, toupdate);

Any thoughts on how to fine tune this so it will work from those of you with any knowledge of constructors and interfaces? I'm thinking that I could be over-thinking this and don't need to carry forward the 2nd interface to subclasses. In fact, I'm thinking I could possibly send all of the DML to a separate class entirely using this kind of method or similar. I just don't have the Java background to know exactly how to do the constructs.

I had also thought about adding a separate static variable for each type of sObject to the Dispatcher class that would look like this:

public static list<Opportunity> opstoupdate = new list<Opportunity>();

I could call them from any of the dependent classes, clear them, call an instance of the DML class, retrieve the "toupdate" static variable for that object and do the DML. My concern would be losing data between instances of the class that would call it (the variable is static and not an instance) if I took that approach. Any thoughts on a work-around to that? Any and all comments are welcome. I feel as though I'm writing a blog here.

Best Answer

This recent blog post by Hari Krishnan might be helpful. He's taken the trigger architecture and used it as a foundation in his framework. It's downloadable and might help to more cleanly separate concerns for you.

But as pbattisson mentioned, the volume of code you posted makes determining the issue difficult. I'd suggest starting fresh with new bare-bones triggers/classes that only solve the specific issue you're having with extra code execution (DML etc.) Or install Hari's unmanaged app in your org and see if it solves the problem.

Also I noticed you're doing an after insert / after update. If you plan on changing Opportunity records you might consider doing this on before update/insert and avoid the extra DML altogether.

Good luck!

Related Topic