[SalesForce] Design Question: Any way around the “System.AsyncException: Future method cannot be called from a future or batch method” error in this situation

Goal:

Attempt to Authenticate then execute authenticated API request with minimal code in a componentized/modularized fashion.


The Players:

Helper_Outbound_API_Request:

Standardized Flow which takes parameters from an sObject and passes to an invocable method and ultimately an API_Handler class.

API_Handler:

Future Class which parses and constructs an API call, then executes the API call, parses the response and starts a flow interview, Helper_Outbound_API_Response.

Helper_Outbound_API_Response:

Takes parameters and generates a log (in a custom object). Has logic to generate a case if API Response is unsatisfactory.


My Approach:

I have a process builder which fires on creation of a record. The process builder ultimately calls the Helper_Outbound_API_Request flow mentioned above – chaining the execution of the following two processes mentioned above (API_Handler and Helper_Outbound_API_Response). Up to this point, everything is working great – record insertion executes API Call and logs response. This initial API Call was for authentication.

I then have another process builder which executes on a Log insert (i.e. where the API Response is stored). This process builder follows a similar process in that it takes that response, parses the information, and passes it to the Helper_Outbound_API_Request to execute another request – the update itself.


The Problem:

Given the secondary process builder was executed off of a @Future method, the second API call is throwing the error:
An Apex error occurred: System.AsyncException: Future method cannot be called from a future or batch method. My expectation was that on save of the Log record the transaction ends and would exit the @Future context – can anyone verify that's incorrect?


I'd like to leave the code generalized so we won't have to write an API Method again… only invoke it via the Helper_Outbound_API_Request I built and handle variances via parameters. Is there any way around my above issue?

The only design work around I can think of here is build in authentication handling to the method, which breaks my requirement of having a single generalized API Method – the authentication payload is going to vary by API call made and therefore require logic to handle any API Call we want to make.

Given I am going to have DML transactions after my authentication request, but before my update request, will that prevent my update request from executing because of transactions in progress? For example, I'm trying to avoid this issue.

Best Answer

I normally check three things before firing a @future method. Note, I learned a lot from reading Advanced Apex Programming by Dan Appleman. The asynchronous patterns are probably his strongest work. You don't have to take everything he says word for word and build the same framework, but here's a simple concept I use. Can't remember if it's exactly in his book or not:

static Boolean shouldProcessAsync()
{
    return !system.isFuture() && !system.isBatch() &&
        Limits.getLimitFutureCalls() > Limits.getFutureCalls();
}

@future
public static void doStuffAsync(Set<Id> recordIds)
{
    doStuff([
        SELECT Name
        FROM MyObject__c
        WHERE Id IN :recordIds
    ]);
}
public static void doStuff(List<MyObject__c> records)
{
    if (records.isEmpty()) return;

    if (shouldProcessAsync())
    {
        doStuffAsync(new Map<Id, SObject>(records).keySet());
    }
    else
    {
        // logic
    }
}

If you implement the above strategy you would just always call the synchronous method, and it will defer to @future if it can.

I really recommend you buy his book. It will teach you much more than anyone could share here.

Related Topic