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.
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 “Using 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.
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 count 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.
In Salesforce, a contact object is associated with an account object. Additionally, every contact can include a corresponding individual 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}"
}
}]
}
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:
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:
For the remainder of this article, I will leverage Postman to explore the concept of composite requests against the Salesforce API.
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 Without Using Salesforce” 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:
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.
Before making any changes to the collection, we need to fork it into our own Postman workspace:
I named my fork “jvc-composite-requests” and used the default values for the other options:
Now, I have a Salesforce APIs collection in my local Postman workspace:
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.
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.
Once completed, a summary screen will be presented:
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.
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:
Now that we have validated connectivity from Postman to Salesforce, we turn our focus to making composite requests.
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:
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}"
}
}
]
}
In plain language, the requirements translate to the following:
Create a new account for Rush
Create an individual record for Bass
Create a contact for Geddy Lee and link the Rush account and individual Bass records
Create an individual record for Guitar
Create a contact for Alex Lifeson and link the Rush account and individual Guitar records
Create an individual record for Drums
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):
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"
}
]
}
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 that 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.
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 that 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!