DEV Community

John Vester
John Vester

Posted on

Composite Requests in Salesforce Are a Great Idea

Article Image

Of all the development eras I’ve witnessed in my 30+ years building apps and features, the RESTful API design pattern is my favorite. Prior to the RESTful approach, I always felt like something was missing when developing web applications.

My concerns were put to rest when I attended the Gartner Enterprise Architecture Summit in 2008. Most notably, a session called “SOAP v REST” was not only informative and funny, but it opened my eyes. I walked away with a desire to understand more about RESTful APIs and soon started experimenting with this new design pattern during my personal time.

A job change was required before I officially started building RESTful APIs—a change which is now more than ten years old. To this day, I still get fired up when I find an impressive API. In fact, an example can be found in my recent publication regarding the Marqeta API for payment processing:

Leveraging Marqeta to Build a Payment Service in Spring Boot

About the Salesforce APIs

It shouldn’t surprise any readers of my past publications that I am quite impressed with the RESTful APIs provided by Salesforce. In fact, I am in the middle of a series called “Leveraging Salesforce Using Spring Boot” which is possible because of the fully featured RESTful API that developers can utilize to meet their needs.

Just recently, the Salesforce Developers site has been refactored to provide a far better experience for developers seeking to utilize the available APIs. Seeing such activity solidifies my belief that Salesforce places lots of value on IT professionals who make Salesforce part of their development repertoire.

Whenever I have the opportunity to integrate with or simply utilize the Salesforce APIs, I look forward to the engagement. In every case, I have walked away learning something new, which results in better APIs for my customers and clientele—even those who have no connections to the Salesforce platform.

Composite Requests

After watching a presentation on composite requests by Philippe Ozil, I immediately saw the value of a composite requests approach, and I could not wait to share it with my readers.

So, what are composite requests, anyway?

In the Salesforce environment:

Composite requests execute a series of REST API requests in a single call. The output of the initial request can be used with the input to a subsequent request. The response bodies and HTTP statuses of the requests are returned in a single response body.

As a result, the entire series of requests counts as a single call toward your API limits, which is something all developers using the Salesforce ecosystem should be aware of when building integrations and applications.

Each subrequest within the composite request includes an httpStatusCode, which maps to the HTTP status code values utilized in standard RESTful communication.

A Simple Composite Request Example

In Salesforce, a contact object is associated with an account object. Additionally, every contact can include a corresponding individual object.

Contact Object

Prior to the existence of composite requests, API developers would first need to POST a new account object, then use the corresponding ID for the new account to POST a new contact object. As a result, two calls would be applied against the underlying organization’s API limits in Salesforce.

Using composite requests, a single POST can be made for both items. Below is an example of the payload:

{
"compositeRequest" : [{
  "method" : "POST",
  "url" : "/services/data/v52.0/sobjects/Account",
  "referenceId" : "refAccount",
  "body" : { "Name" : "Doe’s Widgets" }
  },{
  "method" : "POST",
  "url" : "/services/data/v52.0/sobjects/Contact",
  "referenceId" : "refContact",
  "body" : { 
    "FirstName" : "John",
    "LastName" : "Doe",
    "AccountId" : "@{refAccount.id}"
    }
  }]
}
Enter fullscreen mode Exit fullscreen mode

In this example, a new account for “Doe’s Widgets” will be created and the underlying ID will be used to create a new contact for “John Doe.” The two are associated via the use of the @{refAccount.id} which is established from the referenceId property of the account (line #5).

In fact, it could be possible to create an “individual” object for the John Doe contact using the same approach to set properties like birthdate and current occupation. We will actually implement this very scenario a little later.

You might be asking yourself, when should I use (and not use) composite requests? The following table is intended to act as a quick reference for that question:

Composite Requests Table

Using Postman to Make Composite Requests

Postman is an API platform for building and using APIs. I started using Postman about six years ago, mostly to validate my API designs and exercise the GET, POST, PUT, PATCH, and DELETE methods common to RESTful APIs.

The available functionality in Postman goes well beyond my daily needs, including items such as:

  • API tools—extends my basic usage to include documentation, mocking, testing, and discovery
  • API repository—provides a centralized location where teams can share API artifacts
  • Workspaces—allows for common Postman functionality to be grouped for easy reference
  • Advanced features—improves API operations by leveraging concepts like search, notifications, reporting, alerts, and security warnings

For the remainder of this article, I will leverage Postman to explore the concept of composite requests against the Salesforce API.

Acquiring a Salesforce org

Before we can get started using composite requests with Salesforce, we need an instance of Salesforce to utilize.

For this article, I am going to utilize the Salesforce environment I created for my “Leveraging Salesforce Using Spring Boot” series, which includes the steps necessary to acquire a Salesforce instance (which can be utilized for this article).

If you would prefer to stick with the instructions from Salesforce, the following URL can also get you started:

Create a Developer Org

Configuring Postman

Once Postman is installed, we leverage the Salesforce APIs collection located in a public workspace called Salesforce Developers. The collection includes all of the base functionality we need to log in and make our composite requests.

Configuring Postman

Before making any changes to the collection, we need to fork it into our own Postman workspace:

Configuring Postman

I named my fork “jvc-composite-requests” and used the default values for the other options:

Configuring Postman

Now, I have a Salesforce APIs collection in my local Postman workspace:

Configuring Postman

Logging in to Salesforce

Now, we are ready to log in to Salesforce using OAuth 2.0 via Postman. Navigate to the Authorization tab and scroll down until the Get New Access Token button is visible.

Salesforce Get Access Token

The Get New Access Token button will open a new browser window so you can log in to Salesforce. Once logged in, a modal will appear to allow access to be granted for API requests.

Salesforce Configuration

Once completed, a summary screen will be presented:

Salesforce Summary

The last step is to copy the instance_url value into the _endpoint collection variable, which will point the Postman workspace to the correct Salesforce org.

Validate Salesforce Connectivity

In order to validate connectivity to Salesforce via Postman, I decided to use the Query request which is found in the Salesforce APIs | REST folder. Sending the request resulted in a 200 OK response, along with a list of contacts from my Salesforce instance:

Validate Connectivity

Now that we have validated connectivity from Postman to Salesforce, we turn our focus to making composite requests.

Making Composites Requests Against Salesforce

Building on the example above, I would like to create the following items in Salesforce using a single composite request for one of my favorite bands, Rush:

  • Create a new account called Rush
  • Create a new contact for Rush called Geddy Lee
  • Create a new individual record for Geddy Lee with an occupation of Bass
  • Create a new contact for Rush called Alex Lifeson
  • Create a new individual record for Alex Lifeson with an occupation of Guitar
  • Create a new contact for Rush called Neil Peart Create a new individual record for Neil Peart with an occupation of **Drums*

The payload of the composite request API is as follows:

{
   "compositeRequest": [
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Account",
           "referenceId": "refAccount",
           "body": {
               "Name": "Rush"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Individual",
           "referenceId": "refIndividualGeddy",
           "body": {
               "LastName": "Lee",
               "Occupation": "Bass"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Contact",
           "referenceId": "refContactGeddy",
           "body": {
               "FirstName": "Geddy",
               "LastName": "Lee",
               "AccountId": "@{refAccount.id}",
               "IndividualId": "@{refIndividualGeddy.id}"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Individual",
           "referenceId": "refIndividualAlex",
           "body": {
               "LastName": "Lifeson",
               "Occupation": "Guitar"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Contact",
           "referenceId": "refContactAlex",
           "body": {
               "FirstName": "Alex",
               "LastName": "Lifeson",
               "AccountId": "@{refAccount.id}",
               "IndividualId": "@{refIndividualAlex.id}"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Individual",
           "referenceId": "refIndividualNeil",
           "body": {
               "LastName": "Peart",
               "Occupation": "Drums"
           }
       },
       {
           "method": "POST",
           "url": "/services/data/v52.0/sobjects/Contact",
           "referenceId": "refContactNeil",
           "body": {
               "FirstName": "Neil",
               "LastName": "Peart",
               "AccountId": "@{refAccount.id}",
               "IndividualId": "@{refIndividualNeil.id}"
           }
       }
   ]
}
Enter fullscreen mode Exit fullscreen mode

In plain language, the requirements translate to the following:

  1. Create a new account for Rush
  2. Create an individual record for Bass
  3. Create a contact for Geddy Lee and link the Rush account and individual Bass records 4.Create an individual record for Guitar
  4. Create a contact for Alex Lifeson and link the Rush account and individual Guitar records
  5. Create an individual record for Drums
  6. Create a contact for Neil Peart and link the Rush account and individual Drums records

The request utilized the following POST URI:

{{_endpoint}}/services/data/v{{version}}/composite

After we send the request, we receive an HTTP status of 200 (OK):

Postman Successful

The resulting payload is included below:

{
   "compositeResponse": [
       {
           "body": {
               "id": "0015e00000JcTSMAA3",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Account/0015e00000JcTSMAA3"
           },
           "httpStatusCode": 201,
           "referenceId": "refAccount"
       },
       {
           "body": {
               "id": "0PK5e000000sYlKGAU",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Individual/0PK5e000000sYlKGAU"
           },
           "httpStatusCode": 201,
           "referenceId": "refIndividualGeddy"
       },
       {
           "body": {
               "id": "0035e00000FMahHAAT",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Contact/0035e00000FMahHAAT"
           },
           "httpStatusCode": 201,
           "referenceId": "refContactGeddy"
       },
       {
           "body": {
               "id": "0PK5e000000sYlPGAU",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Individual/0PK5e000000sYlPGAU"
           },
           "httpStatusCode": 201,
           "referenceId": "refIndividualAlex"
       },
       {
           "body": {
               "id": "0035e00000FMahMAAT",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Contact/0035e00000FMahMAAT"
           },
           "httpStatusCode": 201,
           "referenceId": "refContactAlex"
       },
       {
           "body": {
               "id": "0PK5e000000sYlQGAU",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Individual/0PK5e000000sYlQGAU"
           },
           "httpStatusCode": 201,
           "referenceId": "refIndividualNeil"
       },
       {
           "body": {
               "id": "0035e00000FMahNAAT",
               "success": true,
               "errors": []
           },
           "httpHeaders": {
               "Location": "/services/data/v52.0/sobjects/Contact/0035e00000FMahNAAT"
           },
           "httpStatusCode": 201,
           "referenceId": "refContactNeil"
       }
   ]
}
Enter fullscreen mode Exit fullscreen mode

Notice how there is an httpStatusCode for each subrequest being made. This allows the feature or service developer to understand which portions of the request were successful and which failed. In fact, there is even an allOrNone property which controls transaction rollback - which allows for successful items to be kept or all items discarded.

In the example above, I made one API call to Salesforce—instead of seven. This reflects an 85% improvement. This benefit can be further qualified by the avoidance of potential cyclomatic complexity issues that may result when making a request and waiting for the reply before continuing with the next request of related data.

Conclusion

Starting in 2021, I have been trying to live by 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

In this article, we were able to explore the concept of composite requests against the robust Salesforce API. While there is a minor learning curve to understanding the approach, use of composite requests not only packages all related items in a single request, there is an n+1 reduction of API requests counting toward your limits in the Salesforce ecosystem.

Certainly, Salesforce has introduced a service which allows feature and service-tier developers to combine related data in a single request, thereby avoiding the additional programming logic required to make the first request and also waiting for the response before submitting the next request.

Provided your scenario does not exceed 25 subrequests, consideration should be made to employ composite requests when communicating with the Salesforce API. In fact, API developers outside the Salesforce realm should consider learning from the Salesforce team and offering composite requests as a viable option.

See? I ended up learning something new from the Salesforce API just by exploring the idea of composite requests. Happens every time, it seems.

Have a really great day!

Discussion (0)