Idempotency and SalesforceDo you know what idempotence is? In the world of computing, an operation is idempotent when it produces the same result whether you execute it once or multiple times.

Idempotence is an important design consideration when building reliable distributed systems. This post introduces you to idempotence, explains idempotence in the context of Salesforce system integrations, and gives you links to some additional resources that you can review for more information about idempotence.

Designing Distributed Systems with Idempotence in Mind

In a distributed application, you integrate various systems that communicate with one another over a network. To account for inevitable network failures, an important design question that every distributed application architect should consider for each distributed operation is as follows:

     ”Will there be any unwanted side effects if a particular distributed operation executes multiple times?”

Depending on the operation, idempotence may or may not matter. For example, consider the specifications for the overloaded getTemp class method that returns the temperature for a given location.

// return temperature for current time
decimal getTemp(float longitude, float latitude);
// return temperature for a given time
decimal getTemp(float longitude, float latitude, long time);

Can you guess which method is idempotent? The first method signature returns the current temperature for the given location, which can vary when called multiple times with the same input parameter values, and thus is not idempotent. The second method signature always returns the temperature for the given location at the given date and time, and thus is idempotent.

Considering Idempotency in Salesforce Solutions

Now that you have a fundamental understanding of idempotency, it’s time to identify some specific use cases in the context of Salesforce solutions where idempotency is important to consider. If you have others that you would like to highlight, please add your comments to this post.

Developing Idempotent Visualforce Controllers

In the Controller Methods section of the Visualforce Developer’s Guide, there’s a mention of idempotency in the section about Getter Methods.

“It’s a best practice for getter methods to be idempotent, that is, to not have side effects. For example, don’t increment a variable, write a log message, or add a new record to the database. Visualforce doesn’t define the order in which getter methods are called, or how many times they might be called in the course of processing a request. Design your getter methods to produce the same outcome, whether they are called once or multiple times for a single page request.”

Implementing Idempotent API Calls

All objects in Salesforce have standard REST APIs and SOAP APIs that external systems can leverage for system integration. Depending on your requirements, you might need to implement solutions to support idempotent API calls.

For example, consider an external app that needs to create new Opportunities in your Salesforce org using the Force.com REST API. You can simulate this with the following curl commands that call the Force.com REST API.


– Get Token
curl -v -k https://INSTANCE.salesforce.com/services/oauth2/token -d “grant_type=password” -d “client_id=CONSUMERKEY” -d “client_secret=CONSUMERSECRET” -d “username=USERNAME” -d “password=PASSWORDSECURITYTOKEN”

– Test Token (note single quotes around Authorization header to avoid ! escaping)
curl -k https://na9.salesforce.com/services/data/v26.0/sobjects -H ‘Authorization:OAuth TOKEN’ -H “X-PrettyPrint:1″

– Insert new Opportunity with remote order number Id = 3
curl -i -k -H ‘Authorization:OAuth TOKEN’ -H “X-PrettyPrint:1″ -H “Content-Type: application/json” -H “X-HTTP-Method-Override: PUT” -X POST -d ‘{“AccountId”:”ACCOUNTID”, “Name”:”Test”, “StageName”:”Qualification”, “Probability”:”10.0″, “CloseDate”:”2010-06-10″, “OrderNumber__c”:”3″}’ https://INSTANCE.salesforce.com/services/data/v26.0/sobjects/Opportunity

where:

  • INSTANCE = your org’s instance
  • CONSUMERKEY = your remote access application consumer key
  • CONSUMERSECRET = your remote access application consumer secret
  • USERNAME = your username
  • PASSWORD = your password
  • SECURITYTOKEN = your security token
  • TOKEN = the token given to you by the first command
  • ACCOUNTID = a valid AccountId in your org

So what’s the problem? To find out, execute the last curl command multiple times to simulate what happens with a flaky network.

  • The remote system sends the first request, but gets no response because there’s a network problem. However, the call gets through to Salesforce, which executes it and adds the new Opportunity.
  • The remote system retries the request as many times as necessary until it gets a response.
  • Your org now has two or more duplicate Opportunities.

You can probably think of many different ways to make this type of standard API call idempotent. In general, you should choose a solution that is simple and maintainable. To illustrate this general point clearly, consider the following complex and costly solution, and then compare it to the subsequent simple and relatively efficient solution.

  • In your remote application, you could write code that queries Salesforce to see if the Opportunity is already in place. If not, the remote application then makes an API request to create the new Opportunity and update the local database with status information. From a cost point of view, this solution requires that you make one API call to Salesforce for the query, execute a query on Salesforce, and then a second API call to create the Opportunity. Multiply this cost for every retry attempt. Repeat, test, and maintain this logic for every app.
  • Using a couple of mouse clicks, you could add or use a field in the target object to manage remote system state. For example, in a Developer Edition org, the Opportunity object has a custom field, OrderNumber__c. You can customize this field so that it must be unique and required. Then, when any API call from any app tries to create a duplicate Opportunity using an API call, the app gets an error similar to the following that the app can trap and handle appropriately.

[ {
"fields" : [ ],
“message” : “duplicate value found: OrderNumber__c duplicates value on record with id: ID”,
“errorCode” : “DUPLICATE_VALUE”
} ]

Obviously, the second solution is much simpler, centralized, and maintainable than the first solution.

Building Idempotent Apex Web Services

Custom APIs that you build with Apex Web Services support can also be idempotent if you design accordingly. I’ll use this as an opportunity to illustrate how you might handle the more challenging problem of implementing idempotent updates to an Opportunity.

First, you need to understand the problem. Consider what happens if you want to create an API call that increases or decreases the Probability of an Opportunity. If you send the same update multiple times because of a problem with network communication, your service is not idempotent.

To solve the problem, you can create a custom object in your org, MessageLog, that has a custom field called Remote_Message_ID. Then, you can build a custom Apex REST Web service with the following source code.

@RestResource(urlMapping='/manageOpportunity/*')
global class ManageOpportunity {
    // simple class to support JSON serialized return messages
    public class ReturnMessage {
        String message;
        public ReturnMessage(String text) {
            message = text;
        }
    }
    // class to increase or decrease the probability on a given Opportunity
    // the remote caller must provide a unique messageId
    @HttpPatch
    global static String changeProbability(Integer messageId, Id opportunityId, Integer change) {
        // create a new MessageLog object using the given remote messageID
    	MessageLog__c ml = new MessageLog__c(Remote_Message_ID__c = messageId);
        // define a savepoint so that we can rollback everything if necessary
        Savepoint sp = Database.setSavepoint();
        try {
            // insert the messageLog, which will raise a DUPLICATE exception if a corresponding
            // message log record is already present
            insert(ml);
            // create an Opportunity copy using the given opportunityId, which will raise an exception
            // if the corresponding Opportunity is not found
	       	Opportunity o = [SELECT Id, probability FROM Opportunity WHERE Id = :opportunityId LIMIT 1];
            // update the local Opportunity and then update the database, which might raise an exception
            o.probability = o.probability + change;
   	        update(o);
        }
        catch (Exception e) {
            // if anything goes wrong, rollback the entire transaction and return the message to the caller
            Database.rollback(sp);
            // return a JSON serialized returnMessage
            ReturnMessage rm = new ReturnMessage(e.getMessage());
            return JSON.serializePretty(rm);
        }
        // return a JSON serialized returnMessage
        ReturnMessage rm = new ReturnMessage('Update processed');
        return JSON.serializePretty(rm);
    }
}

Assuming your token from the previous curl commands is still valid, you can use the following curl command to try out your new Web service.

— Increase the probability of an Opportunity by 3%
curl -i -k -H ‘Authorization:OAuth TOKEN’ -H “Content-Type: application/json” -X PATCH -d ‘{“messageID”:”1″,”opportunityId”:”OPPORTUNITYID”,”change”:”3″}’ https://na9.salesforce.com/services/apexrest/manageOpportunity

If you execute the same call multiple times, you should see that everyone but the first call returns an error message, indicating that the given message ID has already been received by Salesforce.

“{\n \”message\” : \”Insert failed. First exception on row 0; first error: DUPLICATE_VALUE, duplicate value found: Remote_Message_ID__c duplicates value on record with id: ID: []\”\n}”

 Considering Remote Services for Apex Callouts and Outbound Messages

Apex callouts and outbound messaging are features of Force.com that let you send messages to external Web services: Whenever you use these features, you should consider idempotency. For example, you might design an Apex callout to send state information from your Salesforce org that the remote system can use to identify duplicate messages.

Designing Idempotent Bulk Data Loads

The Force.com Bulk API lets you load, update, and delete large amounts of data in your Salesforce org. When you use this API, you send records to Force.com in batches, and asynchronously receive response information that indicates which rows Salesforce did not process successfully.

The problem that the previous section describes for single row processing also applies to the Bulk API as well: You should implement a solution to prevent duplicate operations from happening, when required. Additionally, your bulk data loading process should also take extra steps to consider rows that return errors, especially when your bulk loading process is approaching Bulk API limits that might prevent you from completing your loads. Rather than just endlessly retrying entire batch loads, you might code your loading process to locally collect and handle rows that returned errors.

Summary

The primary focus of this post is to make you aware of idempotency and how it can be an important design consideration for your Salesforce implementation. Take the time consider all network operations that touch your distributed application system and make a conscious decision about idempotency for each use case. If you have other solutions that you would like to share with us, please add them to the comments of this post. Cheers –Steve

Related Resources

About the Author and CCE Technical Enablement

Steve Bobrowski is a member of Technical Enablement within the Salesforce Customer Centric Engineering group. The team’s mission is to help customers understand how to implement technically sound Salesforce solutions. Check out all of the resources that this team maintains on the Architect Core Resources page of Developer Force.

tagged Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • hanish hatti

    Very interesting and informative post. Thank you

  • http://twitter.com/AlanShanahan Alan Shanahan

    Excellent blog post on a tricky technical subject, in easy-to-follow steps. The value of the strategy is clearly demonstrated by example.