The Salesforce development platform offers many powerful features to provide your team with apps that can unlock new workflows. For many years, the platform has run on a trifecta of technologies: Visualforce (to view the data), Apex (to handle the data), and Salesforce itself (to store the data).
Apex is like many other object-oriented languages, and it has a Java-like syntax. However, Apex runs directly on Salesforce servers, allowing developers to build and deploy an app without worrying about where to host it or how to secure their data.
Applications built on Salesforce run on its multitenant architecture, so server resources like CPU and memory are shared among different organizations. With shared resources, however, wouldn’t it be possible for the code for one org’s app to consume so much processor time and memory space that it affects other users in that same org? An example of this could be a command that takes several minutes to complete.
Salesforce prevents situations like this from occurring by restricting how apps on its platform run. If any app has the potential to interfere with other users, it is restricted from completing its command execution. Long-running programs can potentially interrupt other work by hogging the limited resources available.
If so, how might Salesforce developers build apps that execute long-running commands which can scale with user growth while not impacting performance?
Enter Salesforce Functions, which are small programs written in JavaScript, TypeScript, or Java and deployed onto Salesforce’s servers. Functions can perform the resource-intensive work and be invoked directly from Apex. When the Salesforce Function completes the request, it can send its results back to the originating Apex application, which then performs any additional tasks on those results.
Salesforce Functions run in their own container and don’t affect other tenants on the Salesforce platform. The memory and CPU limits are much higher, allowing you to execute longer running and more complex tasks. Because Salesforce Functions run on the Salesforce platform, security is baked in; there’s no risk of privacy loss as everything runs within the same trust boundary.
In this article, we’ll take a closer look at how to migrate a long-running Apex operation into a Salesforce Function, along with some general tips around the Salesforce DX suite.
This article will require you to have some understanding of both Apex and TypeScript, but don’t worry if you’re not an expert. We’ll go through every piece of code to explain what is happening.
Although the completed version of our code is available as a GitHub repository, you may also want to download some of the CLI tools which Salesforce provides to follow along (or for your own future projects).
Open up the file at force-app/main/default/classes/Dogshow.cls
and take a look at its contents:
public class Dogshow {
public static void updateAccounts() {
// Get the 5 oldest accounts
Account[] accounts = [SELECT Id, Description FROM Account ORDER BY CreatedDate ASC LIMIT 5];
// Set HTTP request
HttpRequest req = new HttpRequest();
req.setEndpoint('https://dog.ceo/api/breeds/image/random');
req.setMethod('GET');
// Create a new HTTP object to send the request object
Http http = new Http();
HTTPResponse res = http.send(req);
String body = res.getBody();
Map<String, String> m = (Map<String, String>) JSON.deserialize(jsonStr, Map<String, String>.class);
String dogUrl = m.get('message');
// loop through accounts and update the Description field
for (Account acct : oldAccounts) {
acct.Description += ' And their favorite dog can be found at ' + dogUrl;
}
// save the change you made
update accounts;
}
}
These 30 lines perform a fairly simple function:
This code is fairly innocuous, but it introduces two situations that present some real problems:
This is an opportunity for a Salesforce Function to take over some of this work. Our Apex class can provide a list of accounts to update to a Salesforce Function. That Salesforce Function can then issue HTTP requests and update the accounts.
Before seeing what that full migration might look like, let’s first write that Salesforce Function.
Using the sfdx CLI, we can easily create a new Salesforce Function in TypeScript using a single command:
$ sf generate function -n dogshowfunction -l typescript
This creates a set of files under functions/dogshowfunction
. The most important of these is index.ts
, which is where our main Salesforce Function code will reside. However, the other files are also important, dealing with testing and linting code, as well as defining TypeScript generation and the Salesforce deployment process.
Let’s focus on index.ts
. In this file, you’ll note that there’s one function exported, and it takes three parameters:
event
: This describes the payload of data that is coming in from your Apex code.context
: This contains the authorization logic necessary to communicate with Salesforce.logger
: This is a simple logger that also integrates with Salesforce.The template code which the CLI generates shows how powerful Salesforce Functions are:
Best of all, since this particular function runs on Node.js, you can install and use any NPM package to supplement your code. Let’s do that right now by installing node-fetch to issue our HTTP request:
$ npm i node-fetch
Our Salesforce Function will be responsible for issuing our HTTP requests and updating our five accounts. To implement that functionality, our function might look something like this:
export default async function execute(event: InvocationEvent<any>, context: Context, logger: Logger): Promise<RecordQueryResult> {
const accounts = event.data.accounts;
accounts.forEach(async (account) => {
const response = await fetch('https://dog.ceo/api/breeds/image/random');
const data = await response.json();
const message = ` And their favorite dog is ${data.message}`
const recordForUpdate = {
type: 'Account',
fields: {
id: account.id,
Description: `${account.Description} ${message}`
}
}
context.org.dataApi.updateRecord(recordForUpdate);
});
}
Let’s break down what’s going on in the code snippet above.
Our event argument is essentially a JSON object which we will define in our Apex code. Although this JSON object doesn’t exist yet, we can assume that it will have the id
and Description
of the account we want to update, based on the behavior of our previous Apex code.
From here, it’s important to note that the context argument is essentially an instantiation of the Node.js SDK for Salesforce Functions. Since we can assume that this account data is provided as an array, we can simply iterate through each item, plugging in the record data as part of our update command.
With our Salesforce Function defined, we can now replace the previous Apex logic with a call to this new function. Issuing a call to a Salesforce Function is surprisingly simple: We only need to know the name of our function and provide the data we want to send it, as shown below:
public class Dogshow {
public static void updateAccounts() {
// Get the 5 oldest accounts
Account[] accounts = [SELECT Id, Description FROM Account ORDER BY CreatedDate ASC LIMIT 5];
// Set up the function
functions.Function dogshowFunction = functions.Function.get('ApexToSalesforceFunctionsMigration.dogshowfunction');
// Create a list to hold the record data
List<Map<String, String>> jsonObj = new List<Map<String, String>>();
for (Account account : accounts) {
Map<String, Object> obj = new Map<String, Object>();
obj.put('id', account.Id);
obj.put('Description', account.Description);
// Add an object to the record list
jsonObj.add(obj);
}
// Send the record list to the function
functions.FunctionInvocation invocation = dogshowFunction.invoke(JSON.Serialize(jsonObj));
// if the function had a return value, it would be available here
invocation.getResponse();
}
}
Just like that, we’ve taken the long-running operation out of our Apex code and offloaded it to a Salesforce Function. Now, we can be certain that our operation will continue to run even though it uses resources more heavily.
The implications of Salesforce Functions cannot be overstated. With Salesforce Functions handling our need for time-consuming, resource-heavy operations, apps built on the Salesforce platform now have much more potential for scaling up seamlessly.
Salesforce Functions provide a way to improve workflows while building effective and durable projects. Without the memory and CPU limits you would experience in Apex, you can leverage Salesforce Functions for the execution of long-running commands, bringing scalability without a negative impact on performance.
We’ve only begun to discuss the amazing capabilities provided by Salesforce Functions. To learn more about them, Salesforce Functions provides plenty of guides detailing specific tasks, along with various Trailhead recipes that are worth looking into.