There are multiple situations where the developer accidentally creates a circular dependency between Apex classes, triggers, or even in object relationships. There are a few programming languages that detect circular dependency between classes during compilation. Unfortunately, Apex can only generate errors at runtime.
We will understand circular dependency using the following two Apex classes:
public class ClassA { public ClassA(){ ClassB b = new ClassB(); System.debug('*** Class A Constructor '); } } public class ClassB { public ClassB() { ClassA a = new ClassA(); System.debug('*** Class B Constructor '); } }
As we can see in the preceding code, a constructor of Class A calls a constructor of Class B, resulting in a error if we try to instantiate any class.
Run the following anonymous code:
ClassA a = new ClassA();
After executing the preceding code, we get the following error:
System.LimitException: Maximum stack depth reached: 1001
A code structure like this, where the two methods directly or indirectly depend on each other is known as the circular dependency anti-pattern.
A developer can create circular dependency accidentally in triggers as well. Assume that the following trigger is written in Account
for the before update
event:
trigger Account_CD on Account (before update) { Set<Id> setAccId = Trigger.newMap.keyset(); //Get all child contacts of Account List<Contact> lstContact = [SELECT NAME FROM Contact WHERE AccountId IN :setAccId]; for(Contact c : lstContact){ c.Description = 'Updated from Account Trigger'; } if(!lstContact.isEmpty()) { update lstContact; } }
In the preceding trigger, whenever any Account
record is updated, we need to update the Description
field in the related child Contacts
.
Now assume that the following trigger is written in the Contact
object for the before update
event:
trigger Contact_CD on Contact (before update) { Set<Id> setAcc = new Set<Id>(); //Get Account Id from Contact for(Contact c : Trigger.New) { if(c.AccountId != null) { setAcc.add(c.AccountId); } } //Get all parent Accounts and update their description field List<Account> lstAcc = [SELECT Name FROM Account Where Id IN :setAcc]; for(Account acc : lstAcc) { acc.Description = 'updated from Contact Trigger'; } if(!lstAcc.isEmpty()) { update lstAcc; } }
The preceding trigger will show that whenever the Contact
is updated on the record, the related parent account's description field will also be updated.
It's time to test these triggers. First, create a sample Account
record and then create a child contact record. When we try to update a child contact, a trigger will be created, and you need to update the parent account record, and the trigger written in the parent account record will again update the child contact record, and thus it will cause a recursion error, as shown in the following screenshot:
We have to be very careful while writing any trigger and this scenario needs to be considered, as this can be caught only at runtime while the end users are using the system.
Circular dependency is detected by Salesforce objects as well out of the box. The Account
object has the standard parent account field. If we try to create a self-relationship by selecting the same account record in the parent account field, then Salesforce generates an error of circular dependency, as shown in the following figure:
In both the preceding examples, we will not get any error. However, we may face issues while deploying classes in the layered approach. The layered approach means deploying the utility classes first followed by the service and controller classes.