DEV Community

John Vester
John Vester

Posted on • Updated on

Leveraging Salesforce Using Spring Boot

Salesforce New York City

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:

Me Eric and the force t-shirt

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.

2015 and an Early Publication

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:

Into The Development Time Machine

In fact, since that time I have published several articles about Salesforce, some of which are noted below:

Using Salesforce as a Service

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.

Our Scenario

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:

  1. Create a Salesforce instance for prototyping a solution.
  2. Establish a mechanism to retrieve and update contacts in Salesforce.
  3. Determine how authentication will work.
  4. Validate the functionality using Postman or simple cURL commands.

What You Need To Know

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.

API Limits Exist

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:

Salesforce API Limits

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.

Authentication Options

Two authentication approaches were considered for this article:

  • User-based Authentication using OAuth2
  • Service-based Authentication (also) using OAuth2

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.

Integration Options

When connecting to Salesforce, there are several options. Over the last six years, I have been able to utilize the following integration options:

  • MuleSoft
  • Heroku Connect
  • Direct Connect to Salesforce RESTful API via client framework
  • Spring Boot and Salesforce RESTful API

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.

Creating a Salesforce Instance

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:

New Developer Account

Once the form was submitted, I received the following email at the address noted above:

Verify Account

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.

Adding a Connected App

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:

  1. Navigate to the Setup link
  2. Navigate to Apps → Apps Manager section on the left-hand menu
  3. Select the New Connected App button
  4. Populate the following properties:
    1. Connected App Name to something like “Spring Boot Integration”
    2. API Name (computed value should be fine)
    3. Contact Email (your email address)
    4. API → Enable OAuth Settings = true
    5. Set callback URL to "https://login.salesforce.com/"
    6. Use OAuth scopes "Access and manage your data (api)" and "Perform requests on your behalf at any time (refresh_token, offline_access)" (for now)
    7. Use "Relax IP restrictions" (for now)
    8. Use "Refresh token is valid until revoked" (for now)
  5. Save the new connected app

Below, is an example of the connected app that I created:

Salesforce Connected App

For clarification, below is an example of the OAuth policies I utilized:

OAuth Policies

Make sure to note the following items for reference later:

  • Consumer Key value
  • Consumer Secret value

Configure Network Access

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.

  1. Obtain your IPv4 address (example: 45.67.100.27)
  2. Navigate to Security → Network Access in Salesforce Setup
  3. Create a new Trusted IP Range which includes your current IP address (I used start address of 45.1.1.1 and 45.255.255.255)

At this point, Salesforce should be set up and ready for use by the Spring Boot service.

Creating 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>
Enter fullscreen mode Exit fullscreen mode

The Spring Boot service will utilize the following two features:

  • RESTful functionality
  • Simple abstract caching

Using the values noted above, a Run configuration was created as shown below:

Spring Boot Run Configuration

Starting the Spring Boot service will display the following information in the console:

Spring Boot Started

With the Salesforce service started, it is time to add the necessary integration classes and methods.

Integrating with Salesforce

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;
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

Building upon these objects, the Spring Boot RESTful service will be designed as shown below:

Spring Boot Integration API

Introducing Caching

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 {
    …
}
Enter fullscreen mode Exit fullscreen mode

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.

Adding a Logging Interceptor

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);
   }
}
Enter fullscreen mode Exit fullscreen mode

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();}
}
Enter fullscreen mode Exit fullscreen mode

During the validation section, the log events shown below will be included in the results.

Validating Functionality

With the Spring Boot service ready for use and running, the next step is to validate our expected functionality.

Getting Salesforce Contacts

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'
Enter fullscreen mode Exit fullscreen mode

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
    }
]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Getting a Single Salesforce Contact

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'
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

This URI is helpful when only a single contact is required.

Updating Contact Information

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"
}'
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Conclusion (Looking Ahead)

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:

  • Svelte
  • Vue.js
  • React (React Native)
  • Angular
  • Lightning Web Components (outside the Salesforce ecosystem)

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!

Latest comments (0)