1

I'm testing out an app that uses a callout to check some info in a government database and writes back any hits to a custom object.

The app is designed to be used via an action on a record page, but I need to fire it automatically via flow. They handed me a code sample to call via a trigger, which i wrapped in an invocable. This works fine on a single record, but fails on a bulk insert.

I've updated my invocable to run down different paths for single vs bulk inserts.

For the bulk path, I am trying to decide on whether to try batch apex with a custom iterator and a batch size of 1, which seems like it should work if I can wrap my head around iterators, or if its possible to do with queueables. At DF there were some statements to the effect that they are trying to make queueables better than batch apex, so seemed like there may be something here.

Copying the code below, but the question is more general. Is there any reason to use Batch Apex (since I need a batch size of 1), or can I use queuables.

Invocable Class (updated to separate callouts from DML)

public class Invocable_OFAC_Checker {
       
    @invocableMethod(label='do the ofac check')
    public static void doTheNeedful(list<Request> requestList){
    
        list<ofacchecker.WSOFACScreeningService.OFACRequestWrapper> reqWrapperList = new list<ofacchecker.WSOFACScreeningService.OFACRequestWrapper>();
        system.debug('in the invocable - req size ' + requestList.size());        
        FOR (request req : requestList){
            
            string sFirstName = req.InputFirstName;
            string sLastName = req.InputLastName;
            string sEntityName = req.InputEntityName;
            string sType = req.inputType;
            string sName = req.inputName;
            string sId = req.inputParentId;
            
ofacchecker.WSOFACScreeningService.OFACRequestWrapper oReqWrapper = new ofacchecker.WSOFACScreeningService.OFACRequestWrapper();
ofacchecker.WSOFACScreeningService.OFACResponseWrapper oResponseWrapper = new ofacchecker.WSOFACScreeningService.OFACResponseWrapper();
oReqWrapper.sFirstName = sFirstName; //Required for Individual Type
oReqWrapper.sLastName = sLastName; //Required for Individual Type
oReqWrapper.sEntityName = sEntityName; //Required for Entity Type
oReqWrapper.sType = sType; // sType can be 'Individual' or 'Entity' depending on requirement
oReqWrapper.sName = sName; //In case of Individual it would be the First Name and Last Name and for Entity it would be the Business Name
oReqWrapper.sParentRecordId = sId; // Record Id of the Parent object

reqWrapperList.add(oReqWrapper);
        }
            
IF(reqWrapperList.size()>=1){
   system.debug('in the req IF ' + reqWrapperList.size());
   list<ofacchecker.WSOFACScreeningService.OFACResponseWrapper> respWrapperList = new list<ofacchecker.WSOFACScreeningService.OFACResponseWrapper>();
    FOR(ofacchecker.WSOFACScreeningService.OFACRequestWrapper reqWrapper : reqWrapperList){
        system.debug('in the callout FOR loop ');
        ofacchecker.WSOFACScreeningService.OFACResponseWrapper oResponseWrapper = new ofacchecker.WSOFACScreeningService.OFACResponseWrapper();
        oResponseWrapper = ofacchecker.WSOFACScreeningService.callOFACService(reqWrapper);    
        respWrapperList.add(oResponseWrapper);        
    }
    FOR(ofacchecker.WSOFACScreeningService.OFACResponseWrapper respWrapper : respWrapperList){
        system.debug('in the DML FOR loop ');        
        ofacchecker.Utility.storeOFACData(respWrapper.oOFAC, respWrapper.listOFACResults);
        ofacchecker.Utility.populateOFACMatchResults(new Set<Id>{respWrapper.oOFAC.Id});
    }
    }

    }    
     public class Request {
    @InvocableVariable(label='First Name' description='Required for Individual Type')
    public String inputFirstName;

    @InvocableVariable(label='Last Name' description='Required for Individual Type')
    public String inputLastName;

    @InvocableVariable(label='Entity Name' description='Required for Entity Type')
    public String inputEntityName;

    @InvocableVariable(label='Type' required=true description='Set to \'Individual\' or \'Entity\' depending on requirement')
    public String inputType;

    @InvocableVariable(label='Name' description='In case of Individual it would be the First Name and Last Name and for Entity it would be the Business Name')
    public String inputName;

    @InvocableVariable(label='Parent Record Id' description='Flow on ofac object used to set custom relationship fields')
    public String inputParentId;
         
  }
    
}
4
  • Your code isn't bulkified. Do all callouts first, then perform the DML operations. This requires multiple for loops. I'd avoid the asynchronous bits when possible, though note that when calling from a flow, that flow could be from a DML operation...
    – sfdcfox
    Sep 27 at 20:35
  • Thanks it's a managed package, and the method doesn't handle a list as an input. Is it possible to do call outs within a for loop? Surely that would hit a limit pretty quickly? How could I avoid async but still bulkify?
    – gorav
    Sep 28 at 11:05
  • refactored to do the callouts first and the dml afterwards. it is working for a 10 record bulk insert, but hits the callout limit on 101.
    – gorav
    Sep 28 at 15:48
  • You'll need to chain possibly, but you can do so in groups of 100 for efficiency.
    – sfdcfox
    Sep 28 at 17:34

1 Answer 1

2

Using a self-chaining queueable is certainly an option, and recent enhancements such as queueable delays and transaction finalizers can help you write more robust code.

If using a batch or queueable, keep "chunk size" as large as possible - even if you need a separate callout per record, you can process 100 records per async execution through 100 callouts - because each costs one async execution and you only get 250000* of those across your whole org per day. See the governor limits documentation.

You might like to consider using data plus Platform Events and an apex or flow platform event subscriber to implement this instead. You can find some discussion around this option over on Apex Hours with an example of how you might implement callouts through this approach in this github repo.

If you do this, you can use Create Records to insert the "Command" record that represents the callout requirement, and you can even use this same flow element to create a platform event record. I.e. no need for an invocable method, since the processing can be invoked through a "Command" record being inserted or a platform event being published in the flow.

3
  • thanks, I'm trying to wrap my head around the design for this. If there are over 100 records to process, I'll pass the list of oReqWrapper to a queueable. it can process the first 100, and then pass the remaining ones to a new instance of the queueable, along with the list of oRespWrapper. once all the callouts are done, it can queue up the DML, perhaps via a finalizer? is that the direction?
    – gorav
    Sep 28 at 17:17
  • ive got something wired up and working, if you are able to let me know if i'm missing something obvious that would be awesome. gist.github.com/goravseth/3090673b3989d0814b0038de8bd802f2
    – gorav
    Sep 28 at 20:30
  • Not looked at the gist, but what you said sounds reasonable - as long as you cannot accidentally have a second queueable also try to process and/or apply DML to the same record(s).
    – Phil W
    Sep 28 at 21:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .