The Blog - Expert Thoughts & Opinions

Apex Continuation

What Is A Continuation?

Before discussing continuations we first need to discuss how callouts work.  A typical callout sends a request via an API to a service, waits for a response, & then returns the requested information (or an error).  

Using the analogy of a restaurant this can be illustrated by a typical transaction. A customer (the client) places an order with a waiter (the API).  The waiter then delivers the request to the kitchen (a web service) & then the food’s ready they deliver it to the client (or maybe they inform the client that they’re all out of what they ordered). One thing to note about these callouts is that there’s an open thread awaiting reply. What this means for our analogy is that the waiter delivers the order to the kitchen & waits there idle until the food is ready to be brought to the table.

Generally speaking, callouts are made and responses are given quite fast. However, in some instances, the information you’re looking for may take some time for the process. For example, say your waiter brings the request to the kitchen for a bowl of soup. The soup is already prepared & heated. A chef scoops some soup into a bowl & hands it to your waiter who brings it to your table. It didn’t really matter that the waiter was idle while the chef was putting the soup into the bowl. Now imagine you order something more complex that the chef needs time to prepare. It doesn’t make sense to have the waiter attending your order while it’s being prepared. If this were the case the restaurant would have to hire many more waiters.

Salesforce is a multi-tenant environment meaning that it operates in a similar way to a restaurant. Your Salesforce org is your table in the restaurant & you share communal assets (the waiters & the kitchen) with other customers. Waiters cost money to hire & it doesn’t make sense for Salesforce to have all their waiters standing in the kitchen while the chefs prepare food for a certain number of people.

Salesforce is, of course, aware of this which is why they’ve implemented concurrent request limits. Each organisation’s concurrent request limit is 10. Once a synchronous Apex request runs longer than 5 seconds, it begins to count against this limit. What they’re effectively saying is that your table (your Salesforce org) can’t have more than 10 waiters standing in the kitchen while your food (your Apex request) is being prepared (unless it’s going to take less than 5 seconds for the waiter to leave your table, get the food from the kitchen, & bring it to your table).

This is where Continuations come into the equation. In Apex a Continuation refers to an asynchronous external callout (a callout that runs in the background). What this means is that the thread you opened when you made a callout becomes dormant while it’s awaiting a response. Reverting to our restaurant analogy this means that the waiter can deliver your order to the kitchen & not stand waiting for it to be prepared. They can go take other orders & bring other food to other clients. When your food has been prepared the kitchen will notify an available waiter who then delivers it to your table.

In Apex we implement this functionality by using the Continuation class.

Why Use Continuations?

In many cases, there’s no issue in using a normal callout to consume an external service. However, in some cases, your org may have many users interacting with the org. You may also have a scenario whereby your Apex request connects with an external service. If this external service takes a number of seconds to respond and if many users are making this request at the same time you run the risk of exceeding the concurrent request limit of 10 (remember if your entire Apex request takes more than 5 seconds it will be counted against this limit).

For example, let's say you’re a financial institution with a number of Visualforce pages that act as custom-built dashboards for a number of different funds. Each page makes a call out to an external REST service to obtain the performance metrics of the fund over a certain timespan. The metrics you’re obtaining from the service are very detailed and extensive, as a result the entire Apex request often exceeds 5 seconds.

Now imagine you have a few hundred users in an office who are constantly clicking between these Visualforce pages and making these requests. The result of this will be error messages in place of data resulting in poor user experience.

This is an instance where we should be utilising the continuation class.  Our Apex request will be made and our callout initiated. Our thread will then lie dormant until our callout receives its reply. We set a callback method within our continuation class meaning that we can define what occurs once the external reply is received.

Implementing Apex Continuations

In the below example we’re making a callout to a fictional web service & displaying it on our page. For the sake of the example, the data being returned only contains two columns – Timestamp & Value. We’re also making a callout to a single web service.  

Please note however that you can add up to three callouts to a single continuation.  To do so simply add a new http request to your continuation instance using the addHttpRequest method.  This is already showcased in the below example.

Notes on the code

  • In order to instantiate an instance of the continuation class, we must call the continuation class constructor.  The constructor accepts a single integer which represents the timeout length.  The maximum timeout is 120 second.
    For example Continuation my_continuation_instance = new Continuation(40);

  •  To add an asynchronous callout to the continuation instance we use the addHttpRequest() method.  This method accepts a http request.
  • To retrieve a callout response we use the getResponse() method.  This method accepts a string which represents the request label value.  In the below example we first set the value for the string MyRequest (this.MyRequest = my_continuation_instance.addHttpRequest(my_http_request)).  We then use MyRequest to retrieve the response (Continuation.getResponse(this.MyRequest)).
  •  As previously mentioned our thread lies dormant until a response is received from the defined callout(s).  We need to tell the continuation instance what to do once this response is returned.  We do this by setting the continuationMethod property

    (my_continuation_instance.continuationMethod='displayCalloutResults').

Visualforce Page

<apex:page controller="MyContinuationController"> 

<apex:form id="result"> <apex:pageBlock> 

<apex:pageBlockSection columns="1"> 

<apex:pageBlockSectionItem> 

<apex:outputPanel rendered="{!FundData != null}"> 

<apex:pageBlockTable value="{!FundData}" var="fund_row"> 

<Apex:column value="{!fund_row.Timestamp}" headervalue="Timestamp"/> 

<Apex:column value="{!fund_row.Value}" headervalue="Fund Value"/> 

</apex:pageBlockTable> 

</apex:outputPanel> 

</apex:pageBlockSectionItem> 

</apex:pageBlockSection> 

</apex:pageBlock> 

</apex:form> 

</apex:page>

Controller

public with sharing class MyContinuationController { 

   public List<FundDataWrapperClass> FundData{get;set;} 
   public String MyRequest; 
   // This is the URL of the service we wish to consume 
   private static final String my_service_endpoint = 'https://www.mysampleendpoint.com'; 

   // The constructor calls the service on load 
   public MyContinuationController() { 
   makeCallout(); 
   } 

   public void makeCallout() { 

   // The number being passed in the Continuation constructor represents the timeout in seconds for our callout. 
   //The maximum timeout we can set is 120. Continuation my_continuation_instance = new Continuation(40); 
   
   // This is where we define what method is to run once a response is received 
   my_continuation_instance.continuationMethod='displayCalloutResults'; 

   // Create callout request 

   HttpRequest my_http_request = new HttpRequest(); 
   my_http_request.setMethod('GET'); 
   my_http_request.setEndpoint(my_service_endpoint); 

   this.MyRequest = my_continuation_instance.addHttpRequest(my_http_request); 
   } 

   private void displayCalloutResults() { 

   HttpResponse my_response = Continuation.getResponse(this.MyRequest); 

   if(my_response.getBody() != null && my_response.getStatusCode() == 200){ 
      FundData = (List<FundDataWrapperClass>)json.deserialize(my_response.getBody(),List<FundDataWrapperClass>.class); 
   } 
   } 

   // This class is used to describe the return format of the HTTP response body 
   private class FundDataWrapperClass { 
      public Datetime Timestamp{get;set;} 
      public String Value{get;set;} 
   } 
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Today's post is by one of our Salesforce Developers, Paraic Cooney .

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

As always, thanks for reading, if you enjoyed this post please feel free to share it and tag us @Pexlify

Pexlify is a Salesforce Platinum Partner in the UK & Ireland. 

If you’re interested in Salesforce Solutions, contact us today to set up a hassle-free consultation.