Presented by Ami Assayag of CRM Science at a Salesforce World Tour event. This deck covers the importance of a mapping engine in an integration solution and the role it plays in translating and connecting data from end to end.
Included within is an architecture overview, support infrastructure, review of different mapping types, as well as code samples.
3. Safe Harbor
Safe harbor statement under the Private Securities Litigation Reform Act of 1995:
This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties
materialize or if any of the assumptions proves incorrect, the results of salesforce.com, inc. could differ materially from the results expressed or
implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking,
including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements
regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded
services or technology developments and customer contracts or use of our services.
The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality
for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results
and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of intellectual property and other
litigation, risks associated with possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating
history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful customer
deployment, our limited history reselling non-salesforce.com products, and utilization and selling to larger enterprise customers. Further
information on potential factors that could affect the financial results of salesforce.com, inc. is included in our annual report on Form 10-Q for the
most recent fiscal quarter ended July 31, 2012. This documents and others containing important disclosures are available on the SEC Filings
section of the Investor Information section of our Web site.
Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available
and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features that
are currently available. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.
4. Agenda
• What is a mapping engine?
• Architecture overview?
• Support infrastructure
• Mapping types
• Code, code and more code!
5. What is a Mapping Engine?
• For the user
– User interface that provide flexibility to how the integration
interacts with the org.
– Allows pre-configuration and changes without development
• Beind the scene
– Supported by a code that handles outbound and inbound
transactions.
6. Why Use Mapping Engines for Integrations?
• Allows for more generic integrations
• Dynamic
– builds callout bodies
– Interprets incoming payloads
• Changes to external service can be handled without additional
development
7. Architecture Overview (Outbound)
Salesforce Org
External
System
Mapping
Data
Callout
Handler
Inbound
Mapping Engine
Outbound
Mapping Engine
Org Data
Apex REST
Trigger/Job
1
2
3
4
5
8. Architecture Overview (Inbound)
Salesforce Org
External
System
Mapping
Data
Callout
Handler
Inbound
Mapping Engine
Outbound
Mapping Engine
Org Data
Apex REST
Trigger/Job
12
3
4
9. Pillars of Mapping Engine Code
• Use object data type instead of specific types
• Cast custom and standard objects to sobject
• Build soql queries dynamically
• Use DMLOptions
10. Infrastructure Choices
• Mapping engines are database driven
• Data infrastructure options
– Custom objects
• Pro: easier to manage relationships
• Con: more directly accessible to users
– Custom setting
• Pro: can be protected, cached
• Con: difficult to manage relationships, requires data configuration
– Custom Metadata Types
• Pro: can include pre-configured data, can be protected, cached
• Con : difficult to manage relationships
11. Mapping Data Structure
• Important: The integration terminology is the “Master”
terminology because Salesforce mapping can be changed.
Object Mapping Object
Integration Object
Salesforce Object
Field Mapping Object
Integration Object
Integration Field
Salesforce Field
Mapping Type
Inbound
Outbound
Value Mapping Object
Integration Field
Integration Value
Salesforce Value
Inbound
Outbound
12. Simplified Field Mapping Data Structure
• Each field mapping record represents the mapping of a single field
between the two systems.
Field Mapping Object
Integration Object
Salesforce Object
Integration Field
Salesforce Field
Mapping Type
Inbound
Outbound
Values Example
= prospect
= Opportunity
= person.first_name
= Contact__r.Name
= Value
= true
= true
13. Mapping Types
• Regular Value – single value of any acceptable data type
• Constant – value that is defined by the mapping, not by the integration
• Value Map – sub mapping for value conversion (normally for picklists)
• Object – mapping of lookup relationships
• Related List – mapping of related list mapping
14. Integration Trigger
// Trigger to handle all callouts for the Opportunity object
trigger ContactIntegration on Contact(after insert) {
ProcessTrigger.sendInsertedRecords('Person', Trigger.newMap.keySet());
}
• Pass to static class
– Integration object name (not salesforce name)
– Triggered record ids (not actual objects because it is not likely to
contain all the information needed.
15. Prepare the Field Mapping
// property to hold field mappings per object
private map<string, list<FieldMapping__c>> fieldMappings;
public list<FieldMapping__c> getFieldMappings(string obj) {
// make sure the variable is instantiated
if (fieldMappings == null)
fieldMappings = new map<string, list<FieldMapping__c>>();
// check if the object mappings was already fetched
if (fieldMappings.get(obj) == null) {
// get the mappings for the object
fieldMappings.put(obj, [SELECT Name, SalesforceObject__c, SalesforceField__c,
IntegrationField__c, MappingType__c,
FROM FieldMapping__c
WHERE IntegrationObject__c = :obj
AND Incoming__c = true //can make this dynamic
LIMIT 1000]);
}
// return the list of mappings for the requested object
return fieldMappings.get(obj);
}
16. Re-Query the Triggered Records
// prepare a set of strings to hold all the field names (set prevents dups)
set<string> fieldsToSelect = new set<string>();
// get the field mapping for the specified object and collect the fields that need to be queried
for (FieldMapping__c mapping : getFieldMappings(objectName)) {
// exclude related list and constant mapping
if (mapping.MappingType__c != 'Related List' && mapping.MappingType__c != ‘Constant')
fieldsToSelect.add(mapping.SalesforceField__c);
}
// start a query string and add the select fields
string query = 'SELECT ';
for (string fieldToSelect : fieldsToSelect)
query += fieldToSelect + ',';
// remove the last comma
query = query.substring(0, query.length()-1);
// add the from and where clause
query += ' FROM ' + objectName + ' WHERE Id IN :' + recordIds;
// dynamically retrieve records into list of sobject
list<sObject> records = database.query(query);
17. Loop Through Records and Mappings
// recieve a list of records and create a json body to call the integration
public list<map<string, object>> processMapping(string objectName, list<sObject> records) {
// define a list of maps that contain value pairs that can be built
// dynamically and serialized.
// the list represents the array of sent records
// each map represents a single record
// the map keyset represent the integration field names
// the map values represent the field values in sfdc
list<map<string, object>> jsonStructure = new list<map<string, object>>();
object fieldValue;
// get the records from the database and loop through the records
for (sObject record : records) {
// prepare a map to hold the value pairs for the current record
map<string, object> outboundRecord = new map<string, object>();
// loop through each mapping record for this object
for (FieldMapping__c mapping : getFieldMappings(objectName)) {
// assemble value pairs
}
}
return jsonStructure ;
}
18. While Looping – Get the Value (Simple Value)
// check if any of the value need to be accessed through a lookup
list<string> FieldParts = fieldName.split('.');
// loop through all the associated objects to drill down to the actual referenced object
integer part = 0;
while (part < FieldParts.size() - 1) {
record = record.getSobject(FieldParts[part]);
// go to next node if the record is valued
if (record == null)
break;
else
part += 1;
}
// make sure the record is not null (example: if accountId is null, Account.name will cause a an exception)
if (record != null) {
// get the value from the record
retValue = record.get(FieldParts[part]);
// the last part is the field itself, use a simple get to return the value as a string.
// if value is null, don't process further (just return null)
if (retValue != null) {
// figure out the field type and do any conversion required by the integration }
}
}
19. While Looping – Add Value Pair
// split the integration field name - may need to add one or more nested objects before adding a value pair.
list<string> node = mapping.IntegrationField__c.split('.');
// prepare a map to hold a parent object. start with the outbound record reference
map<string, object> currentObject = outboundRecord;
// prepare a map to hold a nested object
map<string, object> nestedObject;
// loop through the integration nodes in the path and place each nested object/value
for (integer i = 0; i < node.size(); i++) {
if (i == node.size()-1) {
// this is the last node in the path (the value), so add it unless a dependent field is null
currentObject.put(node[i], fieldValue);
} else {
// check if the nested object already exists
if (currentObject.containsKey(node[i]))
nestedObject = (map<string, object>)(currentObject.get(node[i]));
else
// if the nested object was not found, instantiate one.
nestedObject = new map<string, object>();
// add the nested object to the current object
currentObject.put(node[i], nestedObject);
// make the nested object the next current object
currentObject = nestedObject;
}
}
}
20. Finish the loop and Serialize
. . . .
// if any field values were added for this record, add it to the
// outbound body
if (!outboundRecord.isEmpty())
jsonStructure.add(outboundRecord);
}
// deserialize the list of map so it is ready to be sent. Remove empty objects
if (!jsonStructure.isEmpty())
jsonBody = json.serialize(jsonStructure).remove('{}');
// returned the serialized structure
return jsonBody;
24. While Looping – Get the Value (other types)
// only process mappings that are not of type related list
if (mapping.MappingType__c == 'Constant') {
// this is a string value that is stored in the salesforce field
fieldValue = mapping.SalesforceField__c;
} else if (mapping.MappingType__c == 'Account' || mapping.MappingType__c == 'Contact')
// this is a lookup to a related object
// Get the Id and call processMapping recursively to get object’s value pairs;
} else { // Single Value or Value map
// get the field value from the record
fieldValue = retrieveValue(record, mapping.SalesforceField__c);
// check if the retrieved value needs to be mapped to other values
if (mapping.MappingType__c == 'Value Map')
// get the mapped value from the custom setting
fieldValue = getValueMapping(mapping.Name.toLowerCase(), string.valueof(fieldValue));
}