0

This line works:

Set<Id> ids = (new Map<Id, Contact>([SELECT Id FROM Contact WHERE RecordType.DeveloperName = 'recordTypeName'])).keySet();

This line works:

ws_contact.future_callout(ids);

I am running into a limit because its processing more than 100 callouts. I think the solution is to put this into a for each loop and if I am correct salesforce should automatically chunk it into blocks it can handle.

If the above statement is true then what is the best way to accomplish this so I end up with a Set ids to be able to pass to the future_callout.

Public Class ws_sync
{
    Public static void contact()
    {
        Set<Id> ids = (new Map<Id, Contact>([SELECT Id FROM Contact WHERE RecordType.DeveloperName = 'recordTypeName'])).keySet();

        for (Contact C : [SELECT Id FROM Contact WHERE RecordType.DeveloperName = 'recordTypeName'])

        {
            Set<id> ids = (new Map<id, Contact> (c.id , c.RecordTypeId));
            ws_contact.future_callout(ids);
        }
    }
}

In the Apex Jobs I see the following line:

Status Detail: First error: Too many callouts: 101

Apex class: ws_contact

Apex method: future_callout

I was trying to keep this short and conceptual but perhaps it is best to layout the design with code:

Trigger:

trigger trigger_contact on contact (after insert, after update)
{
    Set<ID> ids = trigger.newMap.keySet();  

    if (trigger.isAfter)
    {
        if (trigger.isInsert || trigger.isUpdate)
        {
            ws_mdm_contact.future_callout(ids);            
        }
    }
}

Apex Class:

public class ws_mdm_contact
{    
    static string thisClassName = 'ws_mdm_contact';

    static map<string, string> mapping = new Map<String, String>
    {
        PJM_GLOBAL.ENVIRONMENT_PRODUCTION => PJM_GLOBAL.namedCred_Prd,
        PJM_GLOBAL.ENVIRONMENT_SFTRAIN => PJM_GLOBAL.namedCred_Train,
        PJM_GLOBAL.ENVIRONMENT_SFSTAGE => PJM_GLOBAL.namedCred_Stage,
        PJM_GLOBAL.ENVIRONMENT_APPCTCDEV2 => PJM_GLOBAL.namedCred_Fake  
    };

    static string Named_Credential;
    static string EndPoint;
    static string JSON_Payload;

//DO INIT
    public static void do_init()
    {
        Named_Credential = mapping.get(PJM_GLOBAL.getEnvironment());
    }

//FUTURE CALLOUT    
    @future(callout=true)    
    Public static void future_callout(Set<ID> ids)
    {
        //determine named credential by environment
        do_init();

        //prepare list to be used for record insert into mdm_logger
        List<mdm_logger__c> mdmLogToInsert = new List<mdm_logger__c>();

        //get list of object records for this list of ids
        List<Contact> lstContact = Helper_MDM.get_Contact(ids);

        //loop through values
        for (Contact Contact_Row : lstContact)
        {
            //conditional check if row qualifies for processing
            if (Contact_Row.Recordtype.DeveloperName == PJM_GLOBAL.CONTACT_RECORDTYPE_CONTACT_MANAGEMENT) 
            {
                //create new log record so we can populate it
                mdm_logger__c logRecord = new mdm_logger__c();

                //create a new record so we can populate it
                DataDef_MDM.Contact Contact_Record = New DataDef_MDM.Contact();

                //populate the record with values
                Contact_Record.id = Contact_Row.Id;
                Contact_Record.firstName = Contact_Row.FirstName;
                Contact_Record.lastName = Contact_Row.LastName;
                Contact_Record.employer = Contact_Row.Employer__c;
                Contact_Record.emailAddress = Contact_Row.Email;
                Contact_Record.groupEmailAddress = Contact_Row.Group_Email__c;
                Contact_Record.preferredEmailAddress = Contact_Row.Preferred_Email__c;
                Contact_Record.phoneNumber = Contact_Row.Phone;
                Contact_Record.phoneType = Contact_Row.Phone_Type__c;
                Contact_Record.alternatePhoneNumber = Contact_Row.Alternate_Phone__c;
                Contact_Record.alternatePhoneType = Contact_Row.Alternate_Phone_Type__c;
                Contact_Record.comments = Contact_Row.Comments__c;
                Contact_Record.title = Contact_Row.Title;

                //serialize the record in JSON format
                JSON_Payload = JSON.serializepretty ( Contact_Record );

                //build endpoint
                EndPoint = Named_Credential;
                EndPoint += '/somepath';
                EndPoint += Contact_Row.Id;

                //HTTP setup
                httpRequest request     = new HttpRequest();
                HttpResponse response   = new HttpResponse();    
                Http http = new Http();    

                //populate request
                request.setEndpoint(EndPoint);        
                request.setMethod('PUT');
                request.setBody(JSON_Payload);
                request.setHeader('Content-Type', 'application/json');
                request.setTimeout(60000);

                //populate field names of log record
                logRecord.Class_Name__c         = thisClassName;
                logRecord.Class_Method__c       = 'future_callout';                
                logRecord.Endpoint__c           = EndPoint;
                logRecord.Endpoint_Method__c    = 'PUT';
                logRecord.Payload__c            = JSON_Payload;

                //assign record to list so we can process it later
                mdmLogToInsert.add(logRecord);

                //send request aka do callout
                try
                {
                    response = http.send ( request );
                    logRecord.Status_Code__c    = response.getStatusCode();
                    logRecord.Status__c         = response.getStatus();
                    logRecord.Message__c        = response.getBody();
                }
                catch (System.CalloutException e)
                {  
                    logRecord.Level__c          = 'ERROR';
                    logRecord.Status_Code__c    = response.getStatusCode();
                    logRecord.Status__c         = e.getMessage();
                    logRecord.Message__c        = e.getStackTraceString();
                }

            } // END IF

        } // END FOR LOOP

       //update list after for loop so it will not interfere with callout
       if (mdmLogToInsert.size()> 0) insert mdmLogToInsert;

    } // END FUTURE CALLOUT

} // END MAIN CLASS

Ultimately what I want to do is write a sync class that will process all contact records and make my callouts.

However, if you see a design flaw then I would appreciate any pointers. I have only been an apex code developer for a few months so I am still learning. This community has been very helpful with the learning curve.

  • It is not clear what your problem is, based on your problem statement so far. Exactly where is the problem? Is it inside of your ws_contact.future_callout() method, or is the ws_sync class your current implementation and the source of your issue? What exactly is the purpose of your for loop? What is this code being called from (a trigger, a visualforce button, etc...)? I think you'd be best off ignoring "chunking" right now, because I don't think you have enough of an understanding about it (and it doesn't act how I think you think it acts). – Derek F Aug 16 '18 at 12:51
  • Details added. ws_contact.future_callout does work but looks like it can only handle 100 callouts before hitting the limit. – Daryn Aug 16 '18 at 13:57
  • Still looking for a reason why your for loop exists in the first place. Can your callout only work on a single Id at a time? If that is the case, then your query outside your loop is useless. If your callout can accept/process many Ids at once, then your entire for loop is unnecessary. – Derek F Aug 16 '18 at 14:33
1

The callout limit is 100 per transaction:

Total number of callouts (HTTP requests or Web services calls) in a transaction: 100

You will need to use a different pattern to perform these callouts in bulk by breaking the set of Ids across transactions and processing them separately to use less than 100 callouts per transaction.

Your class does not make clear in which context(s) it is executing, but this could potentially take the form of a batch class or a Queueable chain pattern. Salesforce will not "chunk" this processing for you; it is your responsibility to ensure your code operates within the governor limits.

The best possible outcome will be if your remote service is able to accept bulk data, rather than requiring a single call per Contact record. If that is the case, you may not have to change your pattern, but simply make use of that API-level bulkification by passing a single collection in a single callout.

Edit: the code you posted indicates the specific constraints of this API.

It is a future method accepting a Set<Id>. It will fire one callout per Id inside the future transaction.

Since you can make up to 50 future calls per transaction, you can theoretically group your Ids into batches of 100 and call future_callout once for each batch. You will be able to do this for up to 50 batches in a transaction before hitting either the callout limit or the future method limit, absent any other limits usage.

The overall callout time limit will still apply, so you may need to tune your batch size down from 100 to get it to consistently execute all the way through depending on the speed of the remote API.

  • Ok let me make sure I am following correctly. The method future_callout can accept bulk data lets say 2000 Ids. Are you saying to control the number of ids to 100 at time before calling the future_callout? So in the trigger and/or in the sync class to send 100 at a time? -or- Are you saying once I get the list of ids to process them in batches of 100 and make the callout? – Daryn Aug 16 '18 at 17:07
  • The method future_callout will come in under the callout limit if you call it with 100 or fewer Ids at a time. You can call it multiple times in a transaction, because each call executes in a separate future transaction. You can call it up to 50 times in a transaction, with each call supplying up to 100 Ids. – David Reed Aug 16 '18 at 17:35
  • Do you know how I might write my Sync class to do that? – Daryn Aug 16 '18 at 19:05
  • @Daryn, this is kind of a Hard Problem (guaranteeing system-to-system sync while sticking to all limits). Even if I could write the code for you, I wouldn't, because it's way too system-specific and there are so many high-level architecture considerations to look at. I would strongly recommend you review the integration architecture and the Salesforce limits issues with your tech lead or architect on this project. – David Reed Aug 16 '18 at 20:49
  • I understand. Thank you for your insights. I think they are looking to me for these roles but being so new to this platform I am trying to lessen my learning curve wherever possible. – Daryn Aug 17 '18 at 1:16

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.