Circular dependencies

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.

Circular dependency in Apex classes

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.

Circular dependency in Apex classes

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.

Circular dependency in triggers

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:

Circular dependency in triggers

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:

Circular dependency in triggers

Other examples

  • A controller calls the service class method and the service class method, in turn, uses the controller's static constant
  • A controller calls the service class method, the service class method calls the utility class method, and the utility class method further calls the controller/service class method, as shown in the following diagram (Fig a):

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.

Other examples

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset