Nav
You are viewing an older version of this section. Click here to navigate to the latest version.

Salesforce to Database Example

Enterprise, CloudHub

This application illustrates how to use batch processing to synchronize Salesforce information with a database. 

Batch

batchBatch Processing allows you to split a payload into individual elements to process each individually. This functionality is particularly useful when working with streaming input or when engineering "near real-time" data integration between SaaS applications.

Database Connector

icon-database-blue-big+%282%29 The Database Connector provides a standardized way to access to any JDBC relational database, consistently using one same interface for every case. This connector allows you to run diverse SQL operations on your database, including Select, Insert, Update, Delete, and even Stored Procedures.

Message Enriching

enriched4+%281%29Mule uses Message Enrichers to enrich message payloads with data (i.e. add to the payload), rather than changing payload contents. Mule enriches a message’s payload so that other message processors in the application can access the original payload.

Assumptions

This document assumes that you are familiar with Mule ESB and the Anypoint Studio interface. To increase your familiarity with Studio, consider completing one or more Anypoint Studio Tutorials. Further, this example assumes you are familiar with XML coding and that you have a basic understanding of http://www.mulesoft.org/documentation/display/current/Mule+Application+Architecture[Mule flows] and SOAP as a Web service paradigm and the practice of WSDL-first Web service development. 

This document describes the details of the example within the context of Anypoint Studio, Mule ESB’s graphical user interface (GUI), and includes configuration details for both the visual and XML editors.

Example Use Case

This application queries a Salesforce account for new or updated contacts at a regular interval, then processes the returned payload one record at a time. It checks to see if a contact currently exists in the database, then updates or creates a new contact accordingly. Once the process is complete for the entire batch, a success message is logged.  

Although this use case could be met without using batch processing – treating the entire list of contacts returned by Salesforce as a whole – using batch makes this process more reliable as any errors that occur in single record will not propagate beyond record level.

Set Up and Run the Example

Complete the following procedure to create, then run this example in your own instance of Anypoint Studio. You can create template applications straight out of the box in Anypoint Studio and tweak the configurations of this use case-based template to create your own customized applications in Mule.

Skip ahead to the next section if you prefer to simply examine this example via screenshots and code snippets.

  1. Create the example application in Anypoint Studio.

  2. Set up Salesforce credentials:

    1. Log in to your Salesforce account. From your account menu (your account is labeled with your name), select Setup.

    2. In the left navigation bar, under the Personal Setup heading, click to expand the My Personal Information folder. 

    3. Click Reset My Security Token. Salesforce resets the token and emails you the new one.

    4. Access the email that Salesforce sent and copy the new token onto your local clipboard.

    5. In the package explorer, open src/main/resources/connector.properties

    6. Complete the file with your own username, password, and security token.

  3. Create a Database and set up credentials:

    1. Create a new MySQL Database

      If you do not have a MySQL database available for your use, you can install MySQL on your local computer. Please visit dev.MySQL.com  to download and install a free version. It is a good idea to also install the MySQL workbench. Please also configure a MySQL username and password for use with this project.
    2. The project requires the follwoing database configuration:

      • MySQL Database Schema: SFtoDB_Example

      • One table: contact

      • Four fields: email,  first_name,  last_name, last_modified

      • One or more rows of data should be inserted into the table

      • A user that must have read and write access to this data.

    3. You can execute the following SQL statement to produce this schema and populate one row of data. See the green box below for tips on executing this SQL.

      
                     
                  
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      USE SFtoDB_Example;
      CREATE TABLE contact (
          ID INT(11) NOT NULL AUTO_INCREMENT,
          email varchar(255) NOT NULL,
          first_name varchar(255) DEFAULT NULL,
          last_name varchar(255) DEFAULT NULL,
          last_modified varchar(255) NOT NULL,
      PRIMARY KEY (email)
      );
      INSERT INTO contacts VALUES (NULL, "leonardmule@mulesoft.com", "Leonard", "Mule", "");

      If you are using the MySQL Workbench, first create the schema SFtoDB_Example , and then execute the above SQL statements from within the MySQL Workbench SQL Editor.

      Alternatively, you can use the MySQL command-line tool, as follows:

      1. Execute this command: CREATE DATABASE SFtoDB_Example;. This will create the database schema.

      2. Save the above SQL code-block to a text file, SQL_for_example.sql

      3. Then execute the SQL with this command: mysql SFtoDB_Example < SQL_for_example.sql

      In either case, you may need to assign a user for access to the schema as well. Please see http://dev.mysql.com/doc/[MySQL Documentation] for more information on using the MySQL Workbench or the command-line tool.

    4. In the package explorer, open src/main/resources/connector.properties

    5. Complete the file with your own DB credentials

  4. In the Package Explorer, right-click the connect-with-salesforce project name, then select Run As > Mule Application. Studio runs the application on the embedded server. 

How It Works

Unlike typical Mule projects that are organized into Flows, this project runs a Batch Process. The process is divided into three stages where actions have different scopes:

Stage Activities

Input

Polls Salesforce at regular intervals for new contacts.

Process Records

Checks if record exists in DB, then updates/creates DB record.

On Complete

Logs a success message.

The Process Records stage is divided into two separate batch steps: the first step checks if the record exists in the DB, the second adds/updates these in the DB. If, while processing a record, the the first step fails, the second step does not process the failed record.

full

Input

Every 30 minutes, the Poll scope triggers a new request to the Salesforce connector. The Salesforce connector is set to perform the query below, where the timestamp flow variable is periodically updated to the time of the last iteration of the poll:


          
       
1
SELECT Email,FirstName,LastModifiedDate,LastName FROM Contact WHERE LastModifiedDate > #[flowVars['timestamp']]

The response returned by the Salesforce connector is a list of contacts.

input


    
             
          
1
2
3
4
5
6
7
&lt;batch:input&gt;
    &lt;poll doc:name="Poll"&gt;
        &lt;fixed-frequency-scheduler frequency="30" startDelay="10" timeUnit="MINUTES"/&gt;
        &lt;watermark default-expression="#['1900-12-11T14:16:00.000Z']" selector="MAX" selector-expression="#[payload.LastModifiedDate]" variable="timestamp"/&gt;
        &lt;sfdc:query config-ref="Salesforce_Configuration" doc:name="Query Salesforce" query="dsql:SELECT Email,FirstName,LastModifiedDate,LastName FROM Contact WHERE LastModifiedDate &gt; #[flowVars['timestamp']]"/&gt;
    &lt;/poll&gt;
&lt;/batch:input&gt;

Process Records

The process records stage of the batch job process the records – each representing a single contact – one at a time. If one of these records fails, the entire task will not fail with it; Mule skips the record, moving on to process the next one.

process

Batch Step 1

In this step, the DataMapper first renames the fields so that they match those in the database. The Database connector issues the following query to the database:


           
        
1
SELECT first_name,last_name,email FROM contact WHERE email=#[payload.email]

Because the Database connector is inside a message enricher scope, Mule does not overwrite the payload with the response from the database query, rather, it adds the response to the message as an additional variable. Thus, all of the information that had originated from Salesforce is retained and can be passed on to the next step.

The message enricher creates two record variables:

  • dbRecord: stores the response of the database query

  • exists: indicates whether a contact already exists in the database, according to the response to the query

step1


    
              
           
1
2
3
4
5
6
7
8
9
10
&lt;batch:step name="Batch_Step1"&gt;
    &lt;data-mapper:transform config-ref="contact_to_map" doc:name="Contact To Map"/&gt;
    &lt;enricher doc:name="Message Enricher"&gt;
        &lt;db:select config-ref="MySQL_Configuration" doc:name="Check existence in Database"&gt;
            &lt;db:parameterized-query&gt;&lt;![CDATA[SELECT first_name,last_name,email FROM contact WHERE email=#[payload.email]]]&gt;&lt;/db:parameterized-query&gt;
        &lt;/db:select&gt;
        &lt;enrich source="#[payload.size() &gt; 0]" target="#[recordVars['exists']]"/&gt;
        &lt;enrich source="#[payload]" target="#[recordVars['dbRecord']]"/&gt;
    &lt;/enricher&gt;
&lt;/batch:step&gt;

Batch Step 2

Mule executes the second batch step only if the first step is successful. Depending on the value the message enricher stored in the flowVar exists (true - the contact exists; false - the contact does not exist) a choice router routes the flow to one of the following processing paths:

  • exists = false: the contact must be added as a new contact. The following insert query is carried out in the database:


           
        
1
INSERT INTO contact (first_name, last_name, email) VALUES (#[payload.first_name],#[payload.last_name],#[payload.email])
  • exists = true: Mule populates the recordVar dbRecord. The following update query is carried out in the database:


           
        
1
UPDATE contact SET first_name=#[payload.first_name],last_name=#[payload.last_name] WHERE email = #[payload.email]
  • If neither of these conditions is met, an error has occurred, so Mule logs a message to announce this error.

step2


    
              
           
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;batch:step name="Batch_Stepx"&gt;
    &lt;choice doc:name="Choice"&gt;
        &lt;when expression="#[recordVars['exists']==false]"&gt;
            &lt;db:insert config-ref="MySQL_Configuration" doc:name="Create contact"&gt;
                &lt;db:parameterized-query&gt;&lt;![CDATA[INSERT INTO contact (first_name, last_name, email) VALUES (#[payload.first_name],#[payload.last_name],#[payload.email])]]&gt;&lt;/db:parameterized-query&gt;
            &lt;/db:insert&gt;
        &lt;/when&gt;
        &lt;when expression="#[recordVars['exists']==true and recordVars['dbRecord'] != null]"&gt;
            &lt;db:update config-ref="MySQL_Configuration" doc:name="Update Contact"&gt;
                &lt;db:parameterized-query&gt;&lt;![CDATA[UPDATE contact SET first_name=#[payload.first_name],last_name=#[payload.last_name] WHERE email = #[payload.email]]]&gt;&lt;/db:parameterized-query&gt;
            &lt;/db:update&gt;
        &lt;/when&gt;
        &lt;otherwise&gt;
            &lt;logger doc:name="Logger" level="INFO" message="Error with #[payload.email] contact"/&gt;
        &lt;/otherwise&gt;
    &lt;/choice&gt;
&lt;/batch:step&gt;

On Complete

The On Complete stage of the batch process executes once, after all of the records have been processed, whether successful, failed or skipped. In this case, a logger announces the completion of the task.

complete


    
             
          
1
2
3
&lt;batch:on-complete&gt;
    &lt;logger doc:name="Log completion" level="INFO" message="Batch sf-&gt;db has finished"/&gt;
&lt;/batch:on-complete&gt;

Complete Code

full


    
            
         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
&lt;mule version="EE-3.5.0" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:batch="http://www.mulesoft.org/schema/mule/batch" xmlns:context="http://www.springframework.org/schema/context" xmlns:data-mapper="http://www.mulesoft.org/schema/mule/ee/data-mapper" xmlns:db="http://www.mulesoft.org/schema/mule/db" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:sap="http://www.mulesoft.org/schema/mule/sap" xmlns:sfdc="http://www.mulesoft.org/schema/mule/sfdc" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/sfdc http://www.mulesoft.org/schema/mule/sfdc/current/mule-sfdc.xsd
http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
http://www.mulesoft.org/schema/mule/db http://www.mulesoft.org/schema/mule/db/current/mule-db.xsd
http://www.mulesoft.org/schema/mule/batch http://www.mulesoft.org/schema/mule/batch/current/mule-batch.xsd
http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd
http://www.mulesoft.org/schema/mule/ee/data-mapper http://www.mulesoft.org/schema/mule/ee/data-mapper/current/mule-data-mapper.xsd
http://www.mulesoft.org/schema/mule/sap http://www.mulesoft.org/schema/mule/sap/current/mule-sap.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-current.xsd"&gt;
      
 
    &lt;data-mapper:config doc:name="contact_to_map" name="contact_to_map" transformationGraphPath="contact_to_map.grf"/&gt;   
     
   &lt;context:property-placeholder location="connectors.properties"/&gt;
    &lt;sfdc:config doc:name="Salesforce" name="Salesforce_Configuration" password="${sfdc.password}" securityToken="${sfdc.securityToken}" username="${sfdc.user}"&gt;
        &lt;sfdc:connection-pooling-profile exhaustedAction="WHEN_EXHAUSTED_GROW" initialisationPolicy="INITIALISE_ONE"/&gt;
    &lt;/sfdc:config&gt;
    &lt;db:mysql-config database="${mysql.database}" doc:name="MySQL Configuration" host="${mysql.host}" name="MySQL_Configuration" password="${mysql.password}" port="3306" user="${mysql.user}"/&gt;
    &lt;batch:job name="salesforce-to-database-Batch1"&gt;
        &lt;batch:threading-profile poolExhaustedAction="WAIT"/&gt;
        &lt;batch:input&gt;
            &lt;poll doc:name="Poll"&gt;
                &lt;fixed-frequency-scheduler frequency="30" startDelay="10" timeUnit="MINUTES"/&gt;
                &lt;watermark default-expression="#['1900-12-11T14:16:00.000Z']" selector="MAX" selector-expression="#[payload.LastModifiedDate]" variable="timestamp"/&gt;
                &lt;sfdc:query config-ref="Salesforce_Configuration" doc:name="Query Salesforce" query="dsql:SELECT Email,FirstName,LastModifiedDate,LastName FROM Contact WHERE LastModifiedDate &gt; #[flowVars['timestamp']]"/&gt;
            &lt;/poll&gt;
        &lt;/batch:input&gt;
        &lt;batch:process-records&gt;
            &lt;batch:step name="Batch_Step1"&gt;
                &lt;data-mapper:transform config-ref="contact_to_map" doc:name="Contact To Map"/&gt;
                &lt;enricher doc:name="Message Enricher"&gt;               
                    &lt;db:select config-ref="MySQL_Configuration" doc:name="Check existence in Database"&gt;
                        &lt;db:parameterized-query&gt;&lt;![CDATA[SELECT first_name,last_name,email FROM contact WHERE email=#[payload.email]]]&gt;&lt;/db:parameterized-query&gt;
                    &lt;/db:select&gt;             
                    &lt;enrich source="#[payload.size() &gt; 0]" target="#[recordVars['exists']]"/&gt;
                    &lt;enrich source="#[payload]" target="#[recordVars['dbRecord']]"/&gt;
                &lt;/enricher&gt;
            &lt;/batch:step&gt;
            &lt;batch:step name="Batch_Stepx"&gt;
                &lt;choice doc:name="Choice"&gt;
                    &lt;when expression="#[recordVars['exists']==false]"&gt;
                        &lt;db:insert config-ref="MySQL_Configuration" doc:name="Create contact"&gt;
                            &lt;db:parameterized-query&gt;&lt;![CDATA[INSERT INTO contact (first_name, last_name, email) VALUES (#[payload.first_name],#[payload.last_name],#[payload.email])]]&gt;&lt;/db:parameterized-query&gt;
                        &lt;/db:insert&gt;
                    &lt;/when&gt;
                    &lt;when expression="#[recordVars['exists']==true and recordVars['dbRecord'] != null]"&gt;
                        &lt;db:update config-ref="MySQL_Configuration" doc:name="Update Contact"&gt;
                            &lt;db:parameterized-query&gt;&lt;![CDATA[UPDATE contact SET first_name=#[payload.first_name],last_name=#[payload.last_name] WHERE email = #[payload.email]]]&gt;&lt;/db:parameterized-query&gt;
                        &lt;/db:update&gt;
                    &lt;/when&gt;
                    &lt;otherwise&gt;
                        &lt;logger doc:name="Logger" level="INFO" message="Error with #[payload.email] contact"/&gt;
                    &lt;/otherwise&gt;
                &lt;/choice&gt;
            &lt;/batch:step&gt;
        &lt;/batch:process-records&gt;
        &lt;batch:on-complete&gt;
            &lt;logger doc:name="Log completion" level="INFO" message="Batch sf-&gt;db has finished"/&gt;
        &lt;/batch:on-complete&gt;
    &lt;/batch:job&gt;
     
&lt;/mule&gt;

See Also