0

I have a scheduled class that calls a Queueable class which does an api callout. What seems to be happening is once the scheduled class is done setting up all of the queueable classes to run, they all run too close together and breaks the system. Is there a way to either space out the queueable classes or how would I account for the 429 error in the catch?

Here is the Scheduled class:

    global void execute(SchedulableContext sc) {
        // Query all of the shipstation stores to update their tracking
        shipstation_store_credential__c[] sscList = [SELECT name, secret__c, key__c, storeID__c, source__c FROM shipstation_store_credential__c];
        // Loop through all of the stores
        for(shipstation_store_credential__C ss : sscList){
            Shipstation_to_totalSales_Service ws = new Shipstation_to_totalSales_Service(ss.name, ss.key__c, ss.secret__c, ss.storeID__c, ss.source__c, '1');
            System.enqueueJob(ws);
            }
    }

}

Here is the Queable class:

public class Shipstation_Service implements Queueable,Database.AllowsCallouts{
public void execute(QueueableContext context) {
         
         try{
             if(source == 'Shopify'){
                 
                tsList =[
                    SELECT  
                            id,
                            External_Id__c,
                            Source__c,
                            Sent_To_Shipstation__c,
                            tracking_number__c,
                            channel_detail__c
                    FROM total_sales__c
                    WHERE Source__c = :source AND channel_detail__c = :name AND tracking_number__c = null
                    ORDER BY CreatedDate DESC];
                 
                String responseBody = getCalloutResponse(); 
                ShipppedShipments trackingShipments = (ShipppedShipments) System.JSON.deserialize(responseBody, ShipppedShipments.class); 
                processOrdersUpdate(trackingShipments);
             }else{
                 String responseBody = getCalloutResponse();
                 ShipppedShipments completeShipments = (ShipppedShipments) System.JSON.deserialize(responseBody, ShipppedShipments.class);
                 processOrdersUpsert(completeShipments);
             }

        } catch(exception e){
            string message = 'ERROR DESERIALIZING RESPONSE' + '\n  ERROR=' + e.getMessage();// Displays an Error message in log if above did not work correctly
            system.debug(message);
            
        }
         
         
            
         
    }

    /**************************************************************************
    * Method used to run the GET method and recieve the Shipmentlist as a JSON
    **************************************************************************/
    public String getCalloutResponse(){
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        String endpoint = 'https://ssapi.shipstation.com/shipments?storeId='+storeID+'&shipDateStart='+ shippingdate+'&shipDateEnd='+shippingdate+'&includeShipmentItems=true&page='+currentPage; 
        request.setEndpoint(endpoint);
        request.setMethod('GET');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        
        String username = key;
        String password = secret;
        
        Blob headerValue = Blob.valueOf(username + ':' + password);
        String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
        request.setHeader('Authorization', authorizationHeader);
        request.setTimeout(60000);
        HttpResponse response = http.send(request);
        system.debug('Response Status Code:'+ response.getStatusCode());
        system.debug('Response Body:'+ response.getBody());
        return response.getBody();  
    }
    
    /***************************************************************************
     * Method used to find the corresponding totalsales record and update it with the tracking number
     ****************************************************************************/ 
    public void processOrdersUpdate(ShipppedShipments trackingShipments){
        
        list<total_sales__c> ordersToUpdate = new list<total_sales__c>();
        for(cls_shipments shipment :trackingShipments.shipments){
            if(shipment.voided == false){
                for(cls_shipmentItems item : shipment.shipmentItems){
                    String shipstationExternalID = shipment.orderNumber + '_' + item.sku;
                    
                    for(total_sales__c record : tsList){
                        if(record.external_id__c == shipstationExternalID){
                            record.tracking_number__c = shipment.trackingNumber;
                            ordersToUpdate.add(record);
                            break;
                        }
                    }
                }
            }else{
                continue;
            }
        }
        try {
            update ordersToUpdate;
              
            Integer currentPageNum = integer.valueOf(trackingShipments.page); 
            Integer totalPagesNum = integer.valueOf(trackingShipments.pages); 
            if(currentPageNum < totalPagesNum){
                Integer nextPageNum = currentPageNum + 1;
                String nextPage = String.valueOf(nextPageNum);
                
                Shipstation_to_totalSales_Service ws = new Shipstation_to_totalsales_Service(name,key, secret, storeID, source, nextPage);
                System.enqueueJob(ws);
            }
        }catch (DmlException e) {
            System.debug(e.getMessage());
        }
    }
    
    
        /***************************************************************************
        * Method used to create a new total sales record from the shipsation information 
        * if it does not already exist
        ****************************************************************************/ 
        public void processOrdersUpsert(ShipppedShipments trackingShipments){
        
        list<total_sales__c> ordersToUpsert = new list<total_sales__c>();
        for(cls_shipments shipment :trackingShipments.shipments)
        {
            if(shipment.voided == false)
            {
                String[] customerName = shipment.shipTo.name.split(' ');
                for(cls_shipmentItems item : shipment.shipmentItems)
                {
                   total_sales__c ts = new total_sales__c();
                      ts.address_1__c          = shipment.shipTo.street1;
                      ts.address_2__c          = shipment.shipTo.street2;
                      ts.channel__c          = 'Website';
                      ts.channel_detail__c     = name;
                      ts.city__c               = shipment.shipTo.city;
                      ts.country__c            = shipment.shipTo.country;
                      ts.division__c           = 'Black Box';
                      ts.email__c              = shipment.customerEmail;
                      ts.first_name__c         = customerName[0];
                      ts.fulfillment_type__c   = 'E-Com';
                      ts.item__c               = item.name;
                      ts.last_name__c          = customerName[customerName.size() -1];
                      ts.lob__c                = 'Distribution';
                      ts.major_channel__c      = 'POS';
                      ts.order_number__c       = shipment.orderNumber;
                      ts.postal_code__c        = shipment.shipTo.postalCode;
                      ts.price__c              = item.unitPrice;
                      ts.qty__c                = item.quantity;
                      ts.sales_type__c         = 'B2C';
                      ts.shipping_amount__c    =  shipment.shipmentCost;
                      ts.sku__c                = item.sku;
                      ts.source__c             = source;
                      ts.state_provence__c     = shipment.shipTo.state;
                      ts.total_sales__c        = item.unitPrice;
                      ts.transaction__c        = 'Purchase';
                      ts.external_id__c        = shipment.orderNumber + '_' + item.sku;
                      ts.tracking_number__c    = shipment.trackingNumber;
                    ordersToUpsert.add(ts);
                }
            }else{
                continue;
            }
        }
        try {
            upsert ordersToUpsert external_id__c;
              
            Integer currentPageNum = integer.valueOf(trackingShipments.page); 
            Integer totalPagesNum = integer.valueOf(trackingShipments.pages); 
            if(currentPageNum < totalPagesNum){
                Integer nextPageNum = currentPageNum + 1;
                String nextPage = String.valueOf(nextPageNum);
                
                Shipstation_to_totalSales_Service ws = new Shipstation_to_totalsales_Service(name,key, secret, storeID, source, nextPage);
                System.enqueueJob(ws);
            }
        }catch (DmlException e) {
            System.debug(e.getMessage());
        }
    }
        
    
}```
0

Option 1: The way to space out the queueables is to serialize them using chained queueables

Change your schedulable execute() to:

public void execute(SchedulableContext sc) {
    // Query all of the shipstation stores to update their tracking
    shipstation_store_credential__c[] sscList = ;
    System.enqueueJob(new Shipstation_to_totalSales_Service(
       new Map<Id, shipstation_store_credential__c> (
          [SELECT name, secret__c, key__c, storeID__c, source__c 
              FROM shipstation_store_credential__c]).keySet();
}

Then, in your queueable,

  • the constructor gets all the Ids and assigns to a local variable
  • the execute method then does the callout and upon return , queues another job again with the same set of ids minus the one just processed. You can see an example of this here

Option 2 - Batchable (Preferred as throughput is likely to be better)

Have your schedulable execute start a batchable with a smaller scope size than the default 200 (one that you know won't kill the remote system)

  • the batchable start() does the query you currently have in the schedulable execute
  • each batchable execute() loops through its presented records and makes the callouts. If the batchable execute's scope is 10, then at most 10 callouts occur at approximately the same time

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.