I've added support for the Composite API to my Salesforce CRM Java client, in particular for updates and inserts. This API method allows for up to 25 subrequests at the cost of just one API call (with a few other limitations depending on the types of calls made). For insertion-only requests, where the items being inserted are not related to each other, the Multiple Record Insertion technique added earlier to the client is probably best, as it allows for up to 200 insertions in one API call.
For tl;dr; purposes, reviewing the testMultipleEntityRecordInsertionsAndCompositeCalls() test case shows how a Composite API call can be made with this client. The sample makes one request consisting of one insert and two updates, and shows how to obtain the fields returned in both success and error scenarios. An example Multiple Record Insertion technique is in the same test.
As shown in the Salesforce docs, composite calls consist of a series of objects with four fields: method
, url
, referenceId
, and body
:
{ "compositeRequest" : [{ "method" : "POST", "url" : "/services/data/v57.0/sobjects/Account", "referenceId" : "refAccount", "body" : { "Name" : "Sample Account" } },{ "method" : "POST", "url" : "/services/data/v57.0/sobjects/Contact", "referenceId" : "refContact", "body" : { "LastName" : "Sample Contact", "AccountId" : "@{refAccount.id}" } }] }
The method will be POST for inserts and PATCH for updates, while the url
field refers to the object being inserted/updated. Example URL formats for Accounts:
Inserts: /services/data/vXX.X/sobjects/Account
Updates: /services/data/vXX.X/sobjects/Account/id/(SF ref ID of Account)
To provide these fields, the client provides an abstract CompositeEntityRecord
, taking care of all fields but the body, the latter to be provided by subclasses the developer creates.
public abstract class CompositeEntityRecord { @JsonIgnore private final String entity; private final String referenceId; private final Method method; // URL dynamically generated when making request private String url; // getters and setters }
The entity is @JsonIgnored
as it is not part of the JSON object sent to Salesforce. As the URL contains the Salesforce API version and other call-specific data, it will be dynamically generated by the SalesforceCompositeRequestService at the time of the call. A sample CompositeEntityRecord subclass, to update an Account's site and number of employees:
public class AccountUpdateCompositeRecord extends CompositeEntityRecord { private Body body = new Body(); public AccountUpdateCompositeRecord(String referenceId) { super("Account", Method.PATCH, referenceId); } public Body getBody() { return body; } public static class Body { public int numberOfEmployees; public String site; // getters and setters } }
For updates, the id should not be placed in the body, that will instead be placed in the url
field at the time of the service call (below). The referenceId needs to be a unique value for all subrequests of the composite request. For updates, the SF ID of the object being updated (unless you're updating one item multiple times in the same call) would be an excellent fit.
Insertions will normally involve more fields, so it will be usually necessary to create another subclass with its additional fields. Also, there won't be a SF ID yet, so just choose a unique string for each reference ID in the composite call. In the response coming back, use the same ID to obtain the subrequest's results.
public class AccountInsertCompositeRecord extends CompositeEntityRecord { private final Body body = new Body(); public AccountInsertCompositeRecord(String referenceId) { super("Account", Method.POST, referenceId); } public Body getBody() { return body; } public static class Body { .... }
The client provides a CompositeEntityRecordRequest
to hold all the subrequests:
public class CompositeEntityRecordRequest { boolean allOrNone; List<? extends CompositeEntityRecord> compositeRequest; public CompositeEntityRecordRequest(boolean allOrNone) { this.allOrNone = allOrNone; } // getters and setters }
See the Salesforce Docs for the usage of allOrNone, due to its importance it is placed in the constructor to require it to be specified. For the response returned by the Composite API call, the CompositeEntityRecordResponse class below is used. The format of the result body returned from Salesforce is unfortunately different in the success (Map) and failure cases (List of Map), so the Result.body field is declared as an Object. However, there are helper methods getSuccessResultsMap()
and getErrorResultsList()
in the Result object to help you parse the body (see the client test case mentioned above for an example of both). By first reading the Result's httpStatusCode
you can determine the proper method to call.
package net.glenmazza.sfclient.model; import java.util.List; import java.util.Map; public class CompositeEntityRecordResponse { List<Result> compositeResponse; // getters and setters public static class Result { private int httpStatusCode; private String referenceId; private Map<String, String> httpHeaders; private Object body; // getters and setters public Map<String, Object> getSuccessResultsMap() { // ... } public List<Map<String, Object>> getErrorResultsList() { // ... } } }
As shown in the test case, once the CompositeEntityRecordRequest object is created, a call to the client's SalesforceCompositeRequestService is straightforward:
CompositeEntityRecordResponse cerr = scrs.bulkProcess(cerReq);
Further Resources:
How to Use Salesforce Composite Rest API - Roycon website
Posted by Glen Mazza in Salesforce CRM at 03:00AM May 12, 2023 | Comments[0]