4

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.

  • 4
    Can you check for system.isFuture() AND system.isBatch() in your code? If so do that and execution will stop even though the PB may have been executed – Eric Feb 6 '17 at 16:53
  • Thanks @Eric. So if I understand correctly, add future/batch check to prevent the secondary API call from triggering; then the PB would trigger it in a separate transaction? – zainogj Feb 6 '17 at 20:49
  • No - The check will stop it altogether if in the same transaction. If the PB was triggered in that transaction then whatever the method did will not occur. It does not cause a second transaction to happen – Eric Feb 6 '17 at 22:09
3

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.

  • Thanks - seems like a great standard to adhere to. I'm not sure it will work for my scenario though. I'll try and expand in my question, but here's the gist. Flow: causes DML > execute Auth API (Future required) > Flow: causes DML > execute Update API (Future required?) > Flow causes DML. I feel like I have a paradox... my update API requires Future because I have a DML before it but can't have Future because it's already Future. Will the DML transactions between Auth and Update API commit in time? – zainogj Feb 6 '17 at 22:04
  • Once you're already in a @future context, further callouts shouldn't require further @future invocations. You're already asynchronous. So that second API call should be fine using this pattern (if memory serves). You can also consider Queueables, etc. Hence why I recommend the book, which has tons of framework suggestions. – Adrian Larson Feb 6 '17 at 22:06
  • Thanks, but second request received a System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out error. I think I remember seeing another post where you mentioned Queueables... I think I'm going to park this project for now, brush up on that book, dig into Queuables and pick this up at a later date. Thanks again. – zainogj Feb 6 '17 at 22:27
  • Thanks @Adrian - you're right about the asynchronous stuff. As mentioned I investigated Queueable context and tried it out. Works great! I was able to successfully build a componentized flow which passes parameters to apex that executes an API call - and chain multiple calls together. I'm going to repurpose some of what you mentioned above to handle if the queue is full or not. – zainogj Apr 24 '17 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.