1

I am writing a SF apex trigger on Opportunity (insert, update) that calls a class function that sends HTTP requests to some Groupcamp instance.

… Quick schema so you're not lost :

  • SF trigger on Opportunity (insert, update)
  • @future GCClass::createRemoteGroup()
  • → sends HttpRequest

The goal is to create one Groupcamp group per SF Opportunity ; it worked relatively fine.

Until they wanted update.

The HttpResponse gives the created group's ID. My idea is to store a ID_groupcamp__c field, so that when it's not empty it runs update procedure instead of insert.

Now consider the SF constraints :

  • @future methods cannot return value
  • we can't pass an object by reference either

And, of course, doing a SOQL query triggers the update which breaks everything.

How to modify an opportunity according to external data ?

Simplified code :

trigger OpportunityToGroupcamp on Opportunity (before insert) {
     for (Opportunity opp : Trigger.new) {
         Groupcamp.new_project(opp.param_1, opp.param_2);
     }
}


public class groupCamp {
    public static void new_project (String param_1, String param_2) {
        String body = '{"some_param": param_1, "other_param": param_2};
        actual_call('POST', '/api/path', body);
    }

    @future public static void actual_call() {
        HttpRequest req = new HttpRequest;
        req.setEndpoint(…);
        req.setBody(…);
        // …

        Http http = new Http;
        HttpResponse res = http.send(req);

        // res contains the Groupcamp ID… how do I insert it into opp ?
    }
}

Thanks in advance !

  • Why can't you update the record in your future call? – Adrian Larson May 31 '16 at 17:39
  • You can pass primitive objects into future calls, such as strings, or lists of strings. To pass more complex objects you can use JSON.serialize to get a string you can deserialize in the future method. – battery.cord May 31 '16 at 17:51
2

You have to update the opportunity in the future call. This means that you'll need to (a) guard against recursive updates (you'll get a fatal error if you try), and (b) optimize the code to fit within the framework limits (50 future calls per transaction, maximum).

This means that your usual design would look like this:

trigger OpportunityToGroupCamp on Opportunity (after insert) {
    GroupCamp.newprojects(Trigger.new);
}

Your class would then be updated like this:

public class GroupCamp {
    public static void newProjects(Opportunity[] records) {
        if(System.isFuture()) {  // No future from future
            return;
        }
        // Future methods can accept collections of primitives
        Id[] recordIds = new Id[0]; 
        for(Opportunity record: records) {
            recordIds.add(record.Id);
            if(recordIds.size() == 100) { // Callout limit is 100
                actualCallout(recordIds);
                recordIds.clear();
            }
        }
        if(recordIds.size() > 0) {  // Send any leftovers.
            actualCallout(recordIds);
        }
    }
    @future static void actualCallout(Id[] recordIds) {
        // Get the fields you need
        Opportunity[] records = [SELECT param_1, param_2 FROM Opportunity
                                 WHERE Id IN :recordIds];
        // Callouts must all occur before DML.
        for(Opportunity record: records) {
            HttpRequest req = new HttpRequest();
            ...
            HttpResponse res = new Http().send(req);
            record.GroupCampId__c = res.getBody(); // Or parse response, etc
        }
        // Remember to check for errors, not illustrated here
        Database.update(records, false);
    }
}
|improve this answer|||||
  • We tried something else… Thanks tho, it is in all likelihood right. – dde Jun 21 '16 at 14:26

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.