In the past, our salesforce.com teams have had to manually re-create records in Dynamics CRM, which robbed them of valuable time. To remedy this, we built an in-house solution to synchronize our CRM data into a single system of record, which we chose to be Dynamics CRM for a couple reasons. We have done some robust customizations to our internal system over the last few years to really make our process flow fit our business, and we also use it as the focal point of our internal project management and time tracking. Instead of rebuilding any of these pieces out again in salesforce.com, it made the most sense to leverage our existing work and push our salesforce.com projects into Dynamics CRM when we needed to start taking advantage of our other solutions.
Here are the steps we took to complete this sync:
2. Mapped / Normalized fields – To start, we had to try to normalize the fields between Account, Opportunity, and Contact objects within salesforce.com and Dynamics CRM. While some of the fields overlapped, we had a number of custom fields that either had subsets of picklist values from the other system or fields that only existed in one system. The effort of creating a field mapping document usually proves to be a painful one, however we were able to cut down a lot of time by leveraging Metablast to generate our Dynamics CRM entity schema data instead of manually looking each entity up. Once we had that, we were able to compare it to the salesforce.com fields and finalize our desired mappings. Some specific Dynamics / salesforce.com gotchas are:
{
"accounts": [
"name" : "Test Account",
"fax" : "555-434-8898",
"Salesforce_ID" : "001E000000JOAXj",
"contacts" : [
"firstName" : "Bob",
"lastName" : "Smith",
"Salesforce_ID" : "003G0000018UpQm"
},
"firstName" : "John",
"lastName" : "Doe",
"Salesforce_ID" : "003G0000017Bugo"
}
],
"opportunities" : [
"name" : "Test Opportunity",
"estimatedValue" : 10000,
"Salesforce_ID" : "006G000000LqXQI"
]
By nesting these records, it allows us to ignore API dependencies the first time through because we can create the Account, and then follow up with the Contact and Opportunity, all from a single request. It also allowed us to do some custom logic when there was a Contact lookup field on the Account, as we were able to do a second pass updating the Account field once the Contact was saved to the system. This approach also made it easier on the salesforce.com side because we did not have to worry about ensuring our Accounts all got created first, then sending over the remainder of our items. It also allowed us to limit the number of outbound HTTP requests we made.
To alleviate the first issue, we decided to push the HTTP requests into future methods. This meant that our sync would happen asynchronously after the records had been saved in salesforce.com, allowing the user to save their record prior to the request being fired. These future calls usually happen within a minute or two (if not immediately), so the delay that we have in exchange for usability is trivial. For the second issue, in order to ensure that all sync errors would continue to be captured without notifying the user, we set up an email service to message an administrator with any errors that occurred. This allowed them to debug behind the scenes and then update the sync later.
public class CRMAccount
public String name;
public String fax;
public String Salesforce_ID;
CRMAccount acct = new CRMAccount();
acct.name = "Test Account";
acct.fax = "555-434-8898";
acct.Salesforce_ID = "001E000000JOAXj";
String body = JSON.serialize(acct);
At this point, the body would look like the following, without needing to manually build the structure:
"Salesforce_ID" : "001E000000JOAXj"
"accountId" : "d8d27735-ba9d-433b-aa5a-dcb0bb2b4a1d",
"errorMessage" : ""
And this would be the simple way to parse that into an object you can immediately reference:
public class CRMResponse
public String accountId;
public String errorMessage;
CRMResponse resp = (CRMResponse)JSON.deserialize(body, CRMResponse.class);
This simple response allows us to accomplish two of our goals:
This particular integration was only a one-way sync, and to prevent changes to these records in Dynamics CRM we ensured that they could only be updated in salesforce.com by disabling those records in Dynamics CRM using Dynamic Forms. This allowed us to identify records with a Salesforce.com ID and differentiate them from Dynamics CRM created records.
If this was a bi-directional sync, we would have needed to implement the solution going the other way, and the logic behind it would have been very similar. Salesforce.com provides an out of the box REST API for record creation, but also allows developers to create custom web services, which would have allowed us to build out a very similar API to the one we built against Dynamics CRM.
Other caveats for companies interested in doing something similar: