Learn Patterns and Best Practices for Apex Developers

As an Apex developer, you have complex business logic implemented in Apex that you’d like to move to Salesforce Functions. Moving your code to a function lets you take advantage of the elastic Salesforce Functions compute environments without worrying about Apex governor limits, like CPU time.

However, since Salesforce Functions code runs “outside” of your org, certain things like transactions and data access work differently from Apex code running in your org. Consider the following development patterns when converting your Apex code into a function.

Good error handling in function code is as important as in any other code you write. We recommend using language semantics (like try/catch/finally) that support error handling.

Be more aware of errors or conditions from:

  • Your own code
  • Library code you import
  • Functions SDK methods
  • HTTP callouts
  • Functions SDK methods or Salesforce REST APIs that relate to standard Salesforce REST API limitations

Monitor API limits such as Record Locks, Concurrency Limits, and General Record Validation errors. For more information see the Salesforce REST API.

Consider routing errors to your users by:

  • Encoding the response returned from the function and allow your Apex logic to route the error accordingly
  • Using the Functions SDK to send a Platform Event (not possible if the root cause is a Salesforce API limit issue)
  • Returning an error status code and short message in the function response and writing to the function log
  • Ensure your Salesforce Admin has set up log drains and is monitoring for error status events

Functions run outside the Apex transaction they were invoked in. Data changes made in a function aren’t automatically tracked as part of a transaction and can’t be rolled back as part of an Apex transaction. Similarly, if your function crashes before committing all data changes, partial changes aren't automatically rolled back, and uncommitted changes aren't automatically tracked. If you need transactions within your function, you must manage transactions yourself.

To manage transactions in a function, use the UnitOfWork SDK class. Use the UnitOfWork SDK class to add data operations to a transaction that can be committed as a single operation. Use the register methods, such as registerCreate() or registeredDelete(), to register data operations and then commitUnitOfWork() to commit the set of operations. Until you commit, nothing is changed in your org’s data.

As an example, suppose you had the following Apex code in a class method:

This method would be treated as one transaction, so if any exception or error occurred between inserts, all data changes made in this method would get automatically rolled back.

If you convert this Apex code into JavaScript using several context.org.data.insert() calls, you run the risk of not being able to roll back data changes if an error occurs. To properly convert the whole Apex transaction into JavaScript, you’d use UnitOfWork to encapsulate the transaction:

Because functions run outside your org in Salesforce Functions compute environments, function code can be run without worrying about Apex compute limits. Design your Apex code to accommodate things like Apex governor CPU limits to improve the efficiency and manageability of your code when you port to Salesforce Functions.

For example, you can opt to use Apex Maps to pre-fetch and pre-process query results to reduce CPU use, but in your function you can avoid this pre-process step. Similarly, if CPU limits hinder operations like date/time comparisons across large data sets, you can do so in a function to avoid hitting a CPU limit.

Follow best practices around making your queries and DML more efficient as data operations from your functions are governed by API call limits, not CPU limits.

Currently API requests from Salesforce Functions are counted against the maximum API requests for a given org.

Orgs using Salesforce Functions are granted 235,000 API requests per day for the sole use by your functions (like a separate bucket of API requests). Importantly, there are other Platform API limits to consider - for example a Salesforce org enforces a limit of 25 active long running API transaction requests.

Unlike Apex, Salesforce Functions use Salesforce Web Service API calls to access org data. There are various API call limits, such as the number of API calls made during a 24-hour period and record size limits. Your Apex code likely wasn’t written with these limits in mind, so consider using the following approaches to avoid excessive API calls in your function:

  • Use UnitOfWork. Along with transaction support, UnitOfWork uses the Composite Graph API to batch together several operations into a single API call.
  • Consider doing data changes in Apex rather than in the function. For example, pass information about records to be changed back from the function to Apex in the function response data. This technique lets you do the actual data operations in the Apex code that originally invoked your function.

The following code sample shows how Objects returned in a callback payload are updated.

  • In some scenarios you can do some data operations in Apex before invoking the function and pass the results to the function via the function invoke payload. Keep your payload size to 6 MB or less (12 MB for asynchronous invocations).
  • For large data operations that can be performed asynchronously, consider using the Bulk API 2.0 from your function. The function payload provides a Salesforce API token for the invoking org that you can use when making HTTP REST API calls to Salesforce APIs like Bulk API 2.0.

It also helps to monitor API usage of your function. Salesforce provides various APIs and tools to check current API usage. For more information on API limits and how to monitor API usage, see Monitoring Your API Usage.

See Limits for more details on API limits and other comparable limits when working with Salesforce Functions.

Your Apex code typically runs as the current user, in Apex system mode, and can have broad object permissions, as described in Understanding Describe Information Permissions. Your function code runs outside of your org and has different permissions. Deployed functions use the Functions permission set to determine object access.

If you're converting your Apex code into function code, review what object permissions your function code needs and update the Functions permission set accordingly.

For more details on Salesforce Functions permissions, see Function Permissions.