You need to sign in to do that
Don't have an account?
Marry Stein
less logic trigger
Hey guys,
i have read several articels about best practice for triggers. Most of the 'rules' are easaly to understand and logical.
One of the rules is 'less logic trigger'. I understand that it doesn't make sense to write very complex triggers. But in my opinion, when i have one trigger per object, at least i have to tell trigger when he needs to fire the specific classes.
To be more specific:
I have three classes, each of them should be fired after a specific update:
- 1 classe creates a copy of the opportunity (fire after closed won)
- 1 class creates different tasks (fire after the owner has changed)
- 1 class for an apex callout (fire after field is updated for the first time)
- 1 class for handle recursion
All of these classes are related to the opportunity object. So i have to create one trigger with the context variable (after update). Sometimes i can handle the logic within the class but often i need the trigger.oldmap variable to make sure, that the trigger act like it should.
so my trigger would look sth like
i have read several articels about best practice for triggers. Most of the 'rules' are easaly to understand and logical.
One of the rules is 'less logic trigger'. I understand that it doesn't make sense to write very complex triggers. But in my opinion, when i have one trigger per object, at least i have to tell trigger when he needs to fire the specific classes.
To be more specific:
I have three classes, each of them should be fired after a specific update:
- 1 classe creates a copy of the opportunity (fire after closed won)
- 1 class creates different tasks (fire after the owner has changed)
- 1 class for an apex callout (fire after field is updated for the first time)
- 1 class for handle recursion
All of these classes are related to the opportunity object. So i have to create one trigger with the context variable (after update). Sometimes i can handle the logic within the class but often i need the trigger.oldmap variable to make sure, that the trigger act like it should.
so my trigger would look sth like
trigger opptrigger on Opportunity (after update) { for (Opportunity opp : trigger.new){ if(!HelperClassTrigger.SetOfIDs.contains(opp.Id)){ Opporuntiy oldOpp = trigger.oldMap.get(opp.Id) if( !oldOpp.isWon && opp.isWon){ // class for copy creation } if( !oldOpp.OwnerId != opp.OwnerId){ // class for owner change } if(String.IsEmpty(oldOpp.Field__c) && !String.IsEmpty(old.Field__c)){ // class for callout } } }
There is another way of writing trigger with Domain Layer and patterns such as Data Mapper, Service Layer and Unit of Work.
https://github.com/financialforcedev/fflib-apex-common
https://github.com/financialforcedev/fflib-apex-common/blob/master/fflib/src/classes/fflib_SObjectDomain.cls
https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_dsl/apex_patterns_dsl_learn_dl_principles
Not sure that could solve your problem but this framework deals with a domain class being shared between trigger invocations and the recursions as well as the changed records more "easily".
thanks for your help ! This solution looks more complex. Is it really best practice to use it ? I definitely will do the trailhead modules but of course i want to deploy my code as soon as possible (considering best practice). If you want and if you have time for that i could share my whole code with you.
You have a single trigger and different states for the opportunity, how to write the code when you prevent the recursion? (by definition the next action is lost if the trigger blocks the recursion too early). I don't have the written solution either.
Your question was about the best practices for a trigger and these enterprise patterns are one of the best options but that must be installed at the beginning of a project ideally (separation of concerns).
The final result for a trigger is very surprising with these patterns because all the triggers contain only ... one line (all the treatment is relocated in other classes).
i dont know if you remember but its not the first time you have helped me :D In my opion its simple to make the trigger work like it should, but i feel a bit uncomfortable about the 'less trigger logic' stuff.
So i have a simple to helper class to prevent recursion
My trigger:
So for each update transaction the trigger checks if it fits some values, if it does, the trigger calls the methode and add the id to the set.
All the triggers that use a domain class instance are just like below:
fflib_SObjectDomain.triggerHandler(Opportunities.class); // one line (always the same) only the parameter (domain class) changes.
The domain class Opportunities.class is instanciated by using the System.Type class and its inner class Constructor (fixed name).
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_type.htm
It is also a Logic-less Trigger (no code inside the trigger itself). It is a standardization of the code but your solution is sufficient.
https://trailhead.salesforce.com/en/content/learn/modules/apex_patterns_dsl/apex_patterns_dsl_learn_dl_principles
Here is an example of how I simplify my triggers.
https://developer.salesforce.com/forums/ForumsMain?id=9062I000000IK8dQAG
Whilst the post was actually about the test code, the trigger example should give you an alternate idea.
My triggers generally have the same simple code to call the handler - eg objHandler.handleBeforeInsert(Trigger.New);
In this way, my triggers are always the same (or similar) structure, with (generally) only the declaration of the objHandler changing between triggers.
Each handler starts with the event handler type method e.g. public void handleBeforeInsert(List<Case> newCases)
which then calls the required actual methods to handle each type of activity.
In this way, I can keep one trigger per object, and one trigger handler per object. With all the code in one handler, I can better handle the execution order, which reduces the chance of any recursion issues. If you already have code in other classes, you could simply call them as required in each event method.
Regards
Andrew