Calling Web Services from a Trigger (and an update to Zillow mashup)

Few months ago, I wrote about accessing an external web service, in this case Zillow to enrich lead data in Salesforce CRM.  Very briefly, Zillow provides the valuation of a property given an address using a proprietary algorithm.  To incorporate this transparently into the application, we built an extension to the standard controller for Lead sObject and defined our own custom save method to override the standard behavior.  The save method we wrote, made the callout to the Zillow service.

Though this works fine for Leads entered through the UI, it doesn't work if a lead were to be entered through the API.  A company may purchase leads from a marketing company and load it through the API – our custom save method will not be called since the Visualforce controller is not instantiated at all.  Triggers provide the ideal place to place this callout logic, since triggers always get invoked when a database operation is involved.  In our case we want to make the callout to the web service when a lead is inserted into the database, either through the API or the UI.  Since Winter 09 release, it is possible to do this – well kind of. Though you can't have the callout code in the trigger itself, you can call a asynchronous method from your trigger code which can then make the web service callout.  Remember that since triggers may be holding database locks etc. you don't want any lengthy operations in the trigger code - a web service callout can potentially be lengthy.  Delegating the callout to an asynchronous method and having the control return immediately to the trigger solves this issue.

Let's look at the trigger code and the asynch method

trigger GetZillowPrice on Lead (after insert) {	Set leadIDs = new Set() ;

	for( Lead l : Trigger.new ){		leadIDs.add( l.Id ) ;	}	AsynchCallZillow.callZillow(leadIDs) ;}

public class AsynchCallZillow {

	@future (callout=true)	public static void callZillow(Set leadIDs){

		Map<ID,Lead> leadMap = new Map<ID,Lead>( [select Lead.Home_Value__c, Lead.Street, Lead.City, Lead.State from Lead where id IN :leadIDs] ) ;

		for( ID i : leadIDs ){			Lead curLead = leadMap.get(i) ;					try{				ZillowService p = new ZillowService() ;				ZillowTypes.PropertySearchResponse r = p.searchZillow( curLead.Street, curLead.City, curLead.State) ;				curLead.Home_Value__c = r.getZEstimateAmount() ;			}			catch( ZillowTypes.ZillowException e){				System.debug( '**** Caught Exception' +e ) ;			}		}		update leadMap.values() ;	}

We have essentially moved the callout code to the asynchronous method and call this method from our trigger.Now our leads will have the Zillow calculated home value estimate irrespective of how the lead gets entered into the system, via the UI or through the API.There are a few things to keep in mind when using this approach.

  • Like any asynchronous method. the callout must be in a static method and return a void type.
  • The system executes the asynchronous call when it has available resources – this means that you can't assume anything about when the call will be executed.
  • The asynchronous method executes in a different transaction context than the trigger. A failure of the callout will have no effect on the transaction controlling the trigger (i.e. changes will not be rolled back).  You will  have to build in suitable compensation logic yourself to account for any exceptions in the callout.  
  •  Keep the governor limits in mind when making callouts. We are constrained by the interface  provided by the remote web service. If the remote web service were to provide any kind of batch processing interface, choose that instead of  making individual calls.  It helps avoid hitting the governor limits and will be more performant as well since there are fewer round trips to make.
  • The impact of these factors ofcourse, depends on the application you are building.  This article has a good introduction to web services and callouts on the Force.com platform.  This thread on our discussion board has some good pointers on testing asynchronous methods.

    tagged Bookmark the permalink. Trackbacks are closed, but you can post a comment.
    • http://www.x2od.com David Schach

      These have been some great posts – good job showing RESTful examples. Using the code here, however, gives me error messages in Eclipse. Is that the way to declare a set in the trigger?
      It works when I change the set to a Set (ID) data type. And It seems to work as well with List (ID).
      –Please replace the parentheses with greater-than/less-than symbols.