I was first introduced to Salesforce during a Gartner Enterprise Architecture summit back in 2008. Full transparency here: the primary reason I attended the presentation was the promise of a cool-looking force.com t-shirt that awaited each of us at the end.
The apparel item did not disappoint, as I have a few historical vacation photos which include me wearing that very item. Here is one of my favorites back in 2010 with my son, Eric:
What also did not disappoint me was the technology built on what was then the force.com platform. Those days were a bit confusing because there was force.com and salesforce.com. I quickly understood they were often differentiated as noted below:
"salesforce.com is generally used to refer to the CRM functionality (the sales, service and marketing applications) and force.com is generally used to refer to the underlying platform (the database, code, and UI on which all the apps are built)"
TechTarget also provides the following definition here. As the years have passed, Salesforce has retired the force.com brand, referring to its platform offerings simply as “Salesforce Platform.” Still, that t-shirt was cool-looking, right?
A couple of years later, I was still working for the company that sent me to the 2008 Gartner summit. It was then that they decided to embrace the use of Salesforce to track items related to the leasing segment of their business. My basic understanding of Salesforce helped establish custom email routing rules that were passing through internal SendMail gateways. However, that was really my only involvement with the Salesforce ecosystem.
Fast-forward seven years to 2015. I was now working as a feature developer, in a full-time corporate role, for a very large automotive conglomerate. After a sprint-worth of feature design that was pretty much introducing a lightweight CRM solution, our team received direction from the corporate office that we should adopt Salesforce instead.
For the next six months, our agile team—clearly in the "performing" phase—was successful at moving from an existing CRM solution to utilizing Salesforce. This work inspired one of my first publications on DZone.com:
https://dzone.com/articles/getting-into-the-development-time-machine
In fact, since that time I have published several articles about Salesforce, some of which are noted below:
While Salesforce is an excellent experience, introducing another user interface is not always an ideal scenario. In fact, back in 2015, our team felt like we were taking a step backward when we presented the (now called "Salesforce Classic") user interface to consumers who were used to a reactive web design.
Salesforce have evolved their user interface since then, first releasing their proprietary Aura framework, before introducing Lightning Web Components, an implementation of the web components standard that can run on it's platform, or be used for your own web application. However, there is still the challenge of asking consumers to adopt yet another application into their daily portfolio of technology solutions.
An alternative approach is to simply utilize Salesforce as a service. After all, Salesforce has provided a robust RESTful API for over 10 years now, which allows access to GET, POST, PUT, and DELETE object data as needed.
The focus of this publication is to provide options on how to leverage the Salesforce API while side-stepping the use of the Salesforce client.
To put things into context for using the Salesforce RESTful API, consider an example where an existing application is already in place. The application provides a majority of the daily functionality required by its users. A major gap, though, is the contact information regarding current and potential clientele.
The feature team recently discovered that all the necessary information exists in Salesforce and there are processes already in place to maintain those contacts. Early indications are that only minor updates will ever be required to a given contact from the existing application.
This article will focus on completing a research spike to accomplish the following items:
Before we get started, there are a few things that I feel like one should know before heading down this path. You know, that “full transparency” thing that I noted regarding the force.com t-shirt in my introduction.
The biggest challenge my feature team faced in 2015 was the number of RESTful API calls that Salesforce allows for every client. Below is a screenshot from the API Request Limits and Allocations page:
In our case, the two items noted above were a big concern for our team. In hindsight, given the understanding of Salesforce and the ability to cache data, I am confident the resulting Salesforce instance would have not exceeded those limitations. However, I wanted this article to highlight that element for those who are deciding when to utilize this approach.
Two authentication approaches were considered for this article:
The determination of when to use either is directly related to the desire (or need) to make requests as a given user that exists in Salesforce. The alternative is to use a service-based approach, where all of the requests originate from a single user that exists in Salesforce.
For this example, the service-based approach will be utilized. As a result, all requests will be completed under the identity of a service-based account in Salesforce.
When connecting to Salesforce, there are several options. Over the last six years, I have been able to utilize the following integration options:
Using MuleSoft and Heroku Connect would provide connectors and deep insight into the Salesforce data domain. While both are excellent solutions, they do require an additional investment since they are subscription-based.
The direct connect option is possible, but a couple of challenges exist. First, service-based authentication is not likely to be an option—because of challenges with housing the login credentials in a secure manner. Secondly, the client will become heavier as Salesforce data is reformatted for digestible use.
As you might expect (and given my publication history), for this example, I am going to utilize the Spring Boot option and leverage the Salesforce RESTful API. I have a high degree of comfort with this approach.
The first step is to create a free developer instance of Salesforce. I was able to get started using the following URL:
https://developer.salesforce.com/signup
This led to a simple form that I had to fill out online:
Once the form was submitted, I received the following email at the address noted above:
The contents of this email were quite helpful, as it provides the URL to my developer instance of Salesforce, plus my username.
After verifying my account, I was required to set a password.
Since there will already be contacts in the developer instance, the base setup for Salesforce is complete.
To connect to Salesforce from the Spring Boot service, a new connected app needs to be created.
The following steps were completed using my developer instance of Salesforce:
Navigate to the Setup link
Navigate to Apps → Apps Manager section on the left-hand menu
Select the New Connected App button
Populate the following properties:
Save the new connected app
Below, is an example of the connected app that I created:
For clarification, below is an example of the OAuth policies I utilized:
Make sure to note the following items for reference later:
An optional (but recommended) step for the prototyping stage is to create a trusted IP range. This can be used both by your instance of the Salesforce client and for the Spring Boot service as well.
Creating a new trusted IP range simply requires knowing your current IP address and following the steps listed below.
At this point, Salesforce should be set up and ready for use by the Spring Boot service.
Using the Spring Initializr from IntelliJ IDEA, a new Spring Boot service called salesforce-integration-service
was created with the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
The Spring Boot service will utilize the following two features:
Using the values noted above, a Run configuration was created as shown below:
Starting the Spring Boot service will display the following information in the console:
With the Salesforce service started, it is time to add the necessary integration classes and methods.
Since the use case for this article is centered on contacts, the following data transformation objects (DTOs) were created in Spring Boot:
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class Contact {
public static final String CONTACT_QUERY = "SELECT Name, Title, Department FROM Contact";
@JsonProperty(value = "Name")
private String name;
@JsonProperty(value = "Title")
private String title;
@JsonProperty(value = "Department")
private String department;
private SalesforceAttributes attributes;
public String getId() {
if (attributes != null && attributes.getUrl() != null) {
return StringUtils.substringAfterLast(attributes.getUrl(), "/");
}
return null;
}
}
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class SalesforceAttributes {
private String type;
private String url;
}
As a result, when a Contact
object is returned, it will include a payload similar to what is displayed below:
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXq0AAE"
},
"id": "0035e000008eXq0AAE",
"Name": "Rose Gonzalez",
"Title": "SVP, Procurement",
"Department": "Procurement"
}
Building upon these objects, the Spring Boot RESTful service will be designed as shown below:
In order to reduce the number of API calls required to retrieve data, I configured the abstract caching included in Spring Boot this way:
@Cacheable("contacts")
public List<Contact> getContacts() throws Exception {
…
}
@CacheEvict(value = "contacts", allEntries = true)
public Contact updateContact(String id, PatchUpdates patchUpdates) throws Exception {
…
}
The methods to retrieve contact information use the @Cacheable
annotation to set/retrieve from the cache when possible. For simplicity in this example, when a contact is updated, the entire cache is evicted using the @CacheEvict
annotation.
To provide insight into the performance of the Spring Boot RESTful service, I created a simple logging interceptor to write messages to the console as API calls are processed.
The first step is to establish the LoggingInterceptor
class:
@Slf4j
public class LoggingInterceptor implements HandlerInterceptor {
private final String loggedStartTimeKey = "_loggedStartingTime";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute(loggedStartTimeKey, startTime);
log.info("Request Started: method={} path={}", request.getMethod(), request.getRequestURI());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
long loggedStartTime = (long) request.getAttribute(loggedStartTimeKey);
long endTime = System.currentTimeMillis();
long timeTakenMs = endTime - loggedStartTime;
log.info("Request Completed: method={} path={} timeTaken={} (milliseconds)", request.getMethod(), request.getRequestURI(), timeTakenMs);
}
}
Next, I updated the WebConfig
class to use the interceptor:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getLoggingInterceptor()).addPathPatterns("/**"); }
@Bean
public LoggingInterceptor getLoggingInterceptor() {
return new LoggingInterceptor();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
}
@Bean
public CloseableHttpClient closeableHttpClient() {return HttpClients.createDefault();}
}
During the validation section, the log events shown below will be included in the results.
With the Spring Boot service ready for use and running, the next step is to validate our expected functionality.
A list of contacts can be retrieved from Salesforce via the Spring Boot RESTful service using the following cURL command:
curl --location --request GET 'http://localhost:9999/contacts'
Once submitted, we receive an HTTP 200 (OK) response, with a full list of contacts using the Contact
DTO created in Spring Boot:
[
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXq0AAE"
},
"id": "0035e000008eXq0AAE",
"Name": "Rose Gonzalez",
"Title": "SVP, Procurement",
"Department": "Procurement"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXqJAAU"
},
"id": "0035e000008eXqJAAU",
"Name": "Jake Llorrac",
"Title": null,
"Department": null
}
]
Please note: To keep the result set concise, only two contact items are shown above.
Viewing the Spring Boot RESTful service logs presents the following information:
2021-06-29 09:03:46.945 INFO 27343 --- [nio-9999-exec-1] c.g.j.s.interceptors.LoggingInterceptor : Request Started: method=GET path=/contacts
2021-06-29 09:03:48.079 INFO 27343 --- [nio-9999-exec-1]
2021-06-29 09:03:47.667 DEBUG 27343 --- [nio-9999-exec-1] c.g.j.s.utils.BearerTokenUtilities : salesforceLoginResult=SalesforceLoginResult(data_goes_here)
2021-06-29 09:03:48.041 DEBUG 27343 --- [nio-9999-exec-1] c.g.j.s.services.ContactService : contacts=[contact_data_goes_here]
c.g.j.s.interceptors.LoggingInterceptor : Request Completed: method=GET path=/contacts timeTaken=1134 (milliseconds)
The initial request took a little over one second to process. To validate that the abstract caching works correctly, I executed the same cURL command again.
This second time, the results were much faster, and there were no calls required to the BearerTokenUtilities
or the ContactService
:
2021-06-29 09:10:04.928 INFO 27343 --- [nio-9999-exec-5] c.g.j.s.interceptors.LoggingInterceptor : Request Started: method=GET path=/contacts
2021-06-29 09:10:04.930 INFO 27343 --- [nio-9999-exec-5] c.g.j.s.interceptors.LoggingInterceptor : Request Completed: method=GET path=/contacts timeTaken=2 (milliseconds)
To retrieve information regarding a single contact from Salesforce, the ID of the contact is required and a similar version of the original cURL command is executed:
curl --location --request GET 'http://localhost:9999/contacts/0035e000008eXq0AAE'
Upon submission, we receive an HTTP 200 (OK) response, with the single Contact
DTO created in Spring Boot provided:
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXq0AAE"
},
"id": "0035e000008eXq0AAE",
"Name": "Rose Gonzalez",
"Title": "SVP, Procurement",
"Department": "Procurement"
}
This URI is helpful when only a single contact is required.
In our use case, there is a need to make only minor changes to the contact data in Salesforce. One example is to change the contact’s title value. In order to make an update to the contact’s title attribute, we would use the following cURL command:
curl --location --request PATCH 'http://localhost:9999/contacts/0035e000008eXq0AAE' \
--header 'Content-Type: application/json' \
--data-raw '{
"Title": "SVP, Procurement 2"
}'
In this example, we are merely changing the title from “SVP, Procurement” to "SVP, Procurement 2."
Upon submitting this request, we receive an HTTP 202 (Accepted) response, along with the updated Contact
DTO:
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXq0AAE"
},
"id": "0035e000008eXq0AAE",
"Name": "Rose Gonzalez",
"Title": "SVP, Procurement 2",
"Department": "Procurement"
}
Since the cache is fully evicted, if we run the original cURL to retrieve all the contacts in Salesforce, the following logs will appear:
2021-06-29 09:18:39.853 INFO 27343 --- [nio-9999-exec-5] c.g.j.s.interceptors.LoggingInterceptor : Request Started: method=GET path=/contacts
2021-06-29 09:18:40.314 DEBUG 27343 --- [nio-9999-exec-5] c.g.j.s.utils.BearerTokenUtilities : salesforceLoginResult=SalesforceLoginResult(data_goes_here)
2021-06-29 09:18:40.416 DEBUG 27343 --- [nio-9999-exec-5] c.g.j.s.services.ContactService : contacts=[contact_data_goes_here]
2021-06-29 09:18:40.418 INFO 27343 --- [nio-9999-exec-5] c.g.j.s.interceptors.LoggingInterceptor : Request Completed: method=GET path=/contacts timeTaken=565 (milliseconds)
Starting in 2021, I have been trying to live the following mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality which extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
J. Vester
Like my feature team discovered in 2015, Salesforce provides an amazing CRM solution—which met the needs of our project at the time and still meets that corporation's needs today. However, there are times when using the entire Salesforce ecosystem is not preferable.
In this article, we created a Spring Boot service example to build upon the well-established Salesforce RESTful API, validating functionality using simple cURL commands. Where possible, the results were cached in order to minimize the use of API calls to the underlying Salesforce API.
If you are interested in the source code used for the Spring Boot service, simply navigate to the following repository on GitLab:
https://gitlab.com/johnjvester/salesforce-integration-service
Future articles will provide examples of how to leverage this Spring Boot service for the following JavaScript-based clients:
These articles will provide high-level examples of how to integrate Salesforce into your current application—without users ever having to log in to Salesforce.
Have a really great day!