Creating your own API

Up to this point in this chapter, we've been using APIs provided by Salesforce. In effect, we're calling into Salesforce to retrieve or manipulate data. These APIs are well thought out, but they have their limitations. For instance, you can create, read, edit, and delete records, but you can only edit and delete individual records. Additionally, the tree creation endpoint, which allows you to create dependent, related objects in a single call, is only in pilot with the Winter '16 release. To help facilitate complex integrations, Salesforce has provided us with the ability to create our own RESTful endpoints, assigning HTTP actions to custom methods that accept custom payloads. We do this by writing custom Apex classes and annotating the class with @RestResource and individual methods with the @httpGet, @httpPatch, @httpPost, and @httpDelete annotations. To illustrate this, let's build a custom REST resource that accepts an opportunity ID and returns the opportunity as well as its opportunity line items and orders. Since this is a request for information, we'll use the GET action. Here's the general framework for what such a class would look like:

@RestResource(urlMapping='/OpportunityApi/*')global with sharing class OpportunityApi {
}

Note the @RestResource annotation and its urlMapping attribute. This tells the system that we want this class to handle endpoints at /services/apexrest/OpportunityApi/. When our class is done and we want to use it, we'll make a GET request to that endpoint with an Opportunity ID specified, as follows:

/services/apexrest/OpportunityApi/006A0000009Yoed.

With that @RestResource annotation, our API class is now set up to handle routing at /opportunityApi/*, but there are no methods for it here. Since RESTful APIs use a combination of the URL and the HTTP actions, we need to add some actions to our class. Let's start with a GET request:

@RestResource(urlMapping='/OpportunityApi/*')
global with sharing class OpportunityApi {

  global class OppApiException extends Exception {}

  global class OppApiWrapper {
    global Opportunity opp {get; set;}
    global List<OpportunityLineItem> oppLineItems {get; set;}
    global List<Order> OppOrders {get; set;}

    public OppApiWrapper(Id oppId){
      List<sObject> oppFields = OpportunityApiFieldDefs__c.getAll().values();
      List<sObject> oppLineItemFields = OpportunityLineItemApiFieldDefs__c.getAll().values();
      List<sObject> oppOrderFields = OpportunityOrderApiFieldDefs__c.getAll().values();
      this.opp = (Opportunity) OpportunityApi.queryForRecords(OpportunityApi.generateSoqlFromCustomSetting(oppFields, 'Opportunity', oppId))[0];
      this.oppLineItems = (List<OpportunityLineItem>)queryForRecords(generateSoqlFromCustomSetting(oppLineItemFields, 'OpportunityLineItem', oppId));
      this.OppOrders = (List<Order>)queryForRecords(generateSoqlFromCustomSetting(oppOrderFields, 'Order', oppId));
    }

  }

  @HttpGet
global static OppApiWrapper GetOppOppLineItemsAndOrders() {
  Id oppId = OpportunityApi.getRequestUriId(RestContext.request);
  if(oppId.getSObjectType().getDescribe().getName().toLowerCase() != 'opportunity'){
    throw new OppApiException('Provided ID was not a valid Opportunity ID! passed OppId is: ' + oppId);
  }
  return new OppApiWrapper(oppId);
}

//Helper methods
 private static Id getRequestUriId(RestRequest req){
   Id UriId;
   try{
     UriId = (ID) req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1);
   } catch (Exception e){
     throw new OppApiException('Failed to parse ID in URI');
   }
   return UriId;
 }
   
 private static string generateSoqlFromCustomSetting(List<sObject> fields, String objectName, Id matchId){
   String retValue = 'SELECT ';
   for(sObject f:fields){
     retValue += String.valueOf(f.get('Name')) + ', ';
   }
   retValue = retValue.removeEnd(', ');
   retValue += 'FROM ' + objectName;
   if(objectName.toLowerCase() == 'opportunity'){
     retValue += 'WHERE Id = '' + matchId + ''';
   } else {
     retValue += 'WHERE OpportunityId = '' + matchId + ''';
   }
   return retValue;
 }

 private static List<sObject> queryForRecords(String query){
   return database.query(query);
 }

}

I added a lot of code to our class this time, so let's go through it bit by bit. If you're going to code your own API methods, you should also include your own exception class, so I've added one. While it's certainly possible to build your APIs without wrapper classes, they come in quite handy here. Because Apex will automatically serialize the returned data to the serialization type specified in your request, a wrapper class becomes incredibly useful when dealing with API calls that return or receive multiple object types. In this case, the wrapper class does most of the work. This is done for a couple of reasons. First, to sanely encapsulate our API integration, and second, to keep our action methods pretty concise and clear. This has the added side effect of enabling further development, but we'll get to that in a minute. Most of this class is actually helper methods and our wrapper class.

Our wrapper class uses custom settings to enable admins to declaratively change the data that's returned by our API call. This is a trick I use wherever I can because enabling admins to change the number or configuration of fields returned by a custom API is a sure fire way to avoid countless please add this field e-mails. It's easy enough to generate a dynamic SOQL statement from the custom settings values, and you can see how to do it down in the helper methods.

Let's look at the GetOppOppLineItemsAndOrders() method. This method is annotated as @HttpGET and it's this annotation, in combination with the URI we request, that determines which method in this class is executed. Note that the GET and DELETE requests cannot accept parameters. You'll have to pull the id out of RestContext. This GET request /services/apexrest/OpportunityApi/006A0000009Yoed would result in the GetOppOppLineItemsAndOrders() method being executed with 006A0000009Yoed as its sole parameter. Here is a diagrammatic representation of the previous example:

Creating your own API

Let there be new records!

Remember I mentioned that using a wrapper class facilitates rapid development? Here's how it works. We've already established a constructor that generates an instance of our wrapper class and returns it to us. If we go one step further, we'll be able to accept JSON representations of our wrapper class for input! What's that step? It is a no operation constructor like this:

//no operation constructor for general deserialization from JSON
public OppApiWrapper(){} 

With this no operation constructor in place, Apex will take our incoming JSON payload and attempt to deserialize it to an instance of our wrapper class. This means that our nested objects can be created with a method like this:

@HttpPost
  global static OppApiWrapper CreateTree(OppApiWrapper incoming) {
  if(incoming == null && incoming.opp != null) {
    throw new OppApiException('Incoming JSON paylaod failed to
      deserialize to an object with an Opportunity key');
  }
  //The point of this is to create object trees in a 
  //transaction, so lets set a savepoint.
  SavePoint tx = Database.setSavepoint();

  //Try inserting just the opportunity. We'll need it's ID 
  //to properly insert the child objects
  try{
    insert incoming.opp;
  } catch (DMLException dmle){
    //if we cannot insert the opp, abort the transaction 
    //and throw exception.
    Database.rollback(tx);
    throw new OppApiException(dmle);
  }
  //Now that we have the opportunities ID we can assign it 
  //to the child objects
  for(OpportunityLineItem oli: incoming.oppLineItems){
    oli.OpportunityId = incoming.opp.id;
  } 
  for(Order o: incoming.oppOrders){
    o.OpportunityId = o.id;
  }
  //now lets try inserting our child objects. 
  //we can do these together.
  //if either fails, abort the transaction and throw an exception.
  try{
    insert incoming.oppLineItems;
    insert incoming.OppOrders;
  } catch (DMLException dmle) {
    Database.rollback(tx);
    throw new OppApiException(dmle);
  }
  //Everything worked great! now lets return a new wrapper 
  //object from our new opportunity
  return new OppApiWrapper(incoming.opp.id);
}

Letting Apex deserialize our incoming JSON into Opportunity and lists of OpportunityLineItems and Quotes allows us to focus on the mechanics and ordering the object's creation. Notice how this method is annotated with @HttpPost. This is the convention for RESTful API creation calls. If we end up throwing an exception, the response back to the client will contain our error message and be marked with an HTTP failure status.

Updating all that data

Convention holds that the GET requests return data, the POST requests create data, and the DELETE requests delete data. Updating data, however, is a bit more complex. In older HTTP servers, the only valid actions were GET, POST, PUT, and Delete. The PUT request became the de facto standard for updating data, if only because it was the only option left. Newer HTTP servers recognize the PATCH action verb for updating data. At the end of the day, however, the differences between PUT and PATCH are only name deep. The convention dictates that PUT and PATCH both function to update data; however, these days, PUT is increasingly rare in API documents. Thus, if we want to update data with our custom API, we should annotate it with @HttpPatch.

For an API to follow RESTful convention, actions that update or delete a record include the record's ID in the URI. In our case, this means that our update request endpoint is built from a PATCH action and a URI that specifies the ID like this:

/services/apexrest/OpportunityApi/006A0000009Yoed.

For Salesforce custom REST APIs, however, that's not required. The JSON payload you send with the PATCH request will usually contain the record ID. So long as your JSON payload keys match the input parameters of your method, Apex will automatically map them for you. Additionally, we can write the @HttpPatch method to accept our wrapper class, meaning our entire patch method looks like this:

@HttpPatch
global static OppApiWrapper remapChildren(OppApiWrapper incoming){
  try {
    upsert incoming.opp;
    upsert incoming.oppLineItems;
    upsert incoming.oppOrders;
  } catch(DMLException dmle){
    throw new OppApiException(dmle);
  }
  return new OppApiWrapper(incoming.opp.id);
}

This wrapper class is now doing a triple duty, allowing us to query, update, and create object trees. The only thing left to do is clean up after we're done with these records.

Another one bites the dust

Deleting data is, perhaps, too easy with custom rest APIs. It's a simple delete DML call. However, knowing what you have to delete and how you want to pass that data in is a bit more complex. In our case, we've created this RESTful API to function with, and on, an entire Object tree. Opportunities are the parent records for both orders and opportunity line items. As a result, the rest call we create only needs to delete the parent opportunity; the system will delete the dependent child objects for us. This isn't always the case, however, and if you needed to delete a tree of records associated through a lookup relationship, you'll need to identify those objects and delete them yourself.

Once you've determined what to delete, you'll need to set up your @httpDelete method to pull the record ID from the URI property of RestContext (or use a common helper method to do it for you). This is because the REST convention dictates that a delete request should be made to a specific record URI, and the Salesforce @httpDelete methods cannot accept parameters. Thus, we'll need to specify the record's ID in the URI as follows:

/services/apexrest/OpportunityApi/006A0000009Yoed

Thus, our delete method looks like this:

@HttpDelete
global static Boolean deleteObjectTree(){
  Boolean result = false;
  id UriID = OpportunityApi.getRequestUriId(RestContext.request);
    try{
        Opportunity toDelete = [SELECT id FROM Opportunity WHERE id = :UriID];
        delete toDelete;
        result = true;
    } catch (DMLException dmle){
        throw new OppApiException(dmle);
    }
  return result;
}

This style of delete method adheres to conventions, but suffers from the same problems the standard sObject API has; you can only delete one record at a time. Throughout this chapter, I've talked about RESTful conventions, not REST rules. While the conventions are there for good reasons, sometimes you just need a bulk-delete method. In these situations, it's useful to remember that we can override these conventions and build our API method with a different action verb. Instead of calling DELETE, for instance, we can call PUT. Since you're the author of these API methods, you can ignore convention and code a PUT method that deletes the records specified in the JSON body. In fact, you can use @HttpPut, @HttpPatch, or @HttpPost to do this. Although you can, you should only overload the PUT, PATCH, and POST methods for destructive changes when you cannot otherwise help it. Let's take a look at what such a bulk-delete method might look like:

@HttpPut
  global static Boolean bulkDelete(List<String> ids){
  Id first = (id)ids[0];
  Schema.SObjectType sobjectType = first.getSObjectType();
  String sobjectName = sobjectType.getDescribe().getName();
  List<sObject> toDelete = Database.query('Select Id From ' + sobjectName + ' Where Id IN :Ids');
    
  boolean SuccessfullyDeleted = false;
  try{
  delete toDelete;
  SuccessfullyDeleted = true;
} catch (DMLException dmle){
  throw new OppApiException(dmle);
}
  return SuccessfullyDeleted;
}

Here, our method is annotated @HttpPut, and we're sending JSON data containing which ID to delete. Our method has to pull sObjects from these IDs in order to delete them, so the majority of our method here isn't in actually deleting, but in finding the records to delete. Note the meta programming around the sObject name. Because we're pulling the sObject off of the first ID, we can technically use this method to delete any kind of object, but we need all the IDs to be of one object type. You can code around this, allowing for a bulk-multi-object delete, but I'll leave that exercise up to you. I want to stress that while this is an option, and one I've used successfully in the past, you should only break the convention if you absolutely have to.

The other way

So far in this chapter, we've used Salesforces' own APIs to manipulate data and we've constructed our own API methods allowing us to do complex operations specific to our org from the API. These are all fundamentally about requesting information from the Salesforce platform. This certainty opens up a world of integration options for us, but sometimes we need to work in the opposite direction. Where Salesforce becomes the client making requests rather than the API being requested. In order to do that, we'll need a good understanding of what it means to make http(s) calls from within Salesforce and how to handle the received responses.

As in the Salesforce world, so too external APIs tend to come in two formats: SOAP and REST. If you're making SOAP API requests, remember that the platform's WSDL to Apex converter will build not only your serialization and sending code, but reception and deserialization code all from your vendor provided API WSDL. However, if you're integrating with a REST API, we need to do some of the heavy lifting ourselves.

Heavy lifting

Making a callout from Apex involves three different objects: A request object, an Http object, and the response object. We'll need to make a request object, and in return, we'll get a response object from the http object when we send that request. When we create the request, we have to set the endpoint, the action, and any required headers. Additionally, we can send a JSON or XML payload body if required. Let's construct a simple get request to Google to demonstrate the mechanics:

public string getRequest(){
  HttpRequest req = new HttpRequest();
  //Set HTTPRequest Method
  req.setMethod('GET');
  req.setEndpoint('https://www.google.com?q=codefriar.com');
  //Create the http object
  Http http = new Http();

  try {
    //This executes the rest request
 HTTPResponse res = http.send(req);
 //Dump result to logs for debugging
 System.debug(res.toString());
  } catch(System.CalloutException e) {
 //If it fails, handle it responsibly
  }
}

This may seem cumbersome and verbose at first glance, but in reality, it's quite elegant, if very low level. To make this easier on ourselves, we need to create some intelligent abstractions on these calls. Ultimately, it'd be pretty handy to have .post(), .get(), .patch(), and .delete() methods at our disposal. I tend to refer to libraries like this as RestClients. Building such a RestClient would allow us to quickly and easily make REST requests regardless of the API we were integrating with. If we build it with object-oriented inheritance in mind, we'll have a library we can extend into specific classes for individual APIs. In the end, our call out REST architecture would look like this:

Heavy lifting

To build this, let's think through what properties, constructors, and methods a RestClient will need. Initially, we'll need properties for endpoint, body, method, and headers. These properties work together to provide all that is needed to make a REST request. We'll need a request generator method and execution and response handling methods. Since we'll want to be able to extend this, our class should also be a virtual class. Here's an example of what a basic RestClient would look like:

public with sharing virtual class RestClient {
 Public class RestClientException extends Exception {}

 Public Map<String, String> headers = new Map<String, String>();
 Public String endpoint = '';
 Public String method = '';
 Public String body = '';
 Public HttpRequest request;
 Public HttpResponse response;
 Public Integer responseCode;
 Public String responseBody;
 Public Http httpObj;
 Public String clientCert;

 Public Static Final Integer HTTP_MAX_REQUEST_TIME = 120000;
 Public Static Final Boolean ENABLE_DEBUG_LOGS = TRUE;
 /*
  * Helper Methods - These do the actual work.
  */

 Public void buildRequest() {
  if (ENABLE_DEBUG_LOGS) {
   system.debug('Restclient buildRequest details' + headers + '
' +
     endpoint + '
' + method + '
' + body + 
     'set RestClient.ENABLE_DEBUG_LOGS = false to suppress this
      message');
  }
  //Create the httpRequest Object
  HttpRequest request = new HttpRequest();
  //Set the max timeout
  request.setTimeout(HTTP_MAX_REQUEST_TIME); 
  //Given a map<String,String> of header keys and values set 
  //them as request headers
  if (this.headers != null) {
   for (String headerName : this.headers.keySet()) {
    request.setHeader(headerName, headers.get(hkey));
   }
  }
  //Set the Endpoint
  request.setEndpoint(this.endpoint);
  //Set the Method
  request.setMethod(this.method);
  //Optionally set the body, if it's not blank or empty
  if (String.isNotBlank(this.body) && String.isNotEmpty(this.body)) {
   request.setBody(this.body);
  }
  this.request = request;
 }

Public void executeRequest() {
    if(this.request == null){
      this.buildRequest();
    }
    this.response = this.httpObj.send(request);
    if (response.getStatusCode() > 299) {
throw new RestClientException('Response Code was: ' +response.getStatusCode());
    }
  }
}

With this code, we've established some abstractions on the standard http callout functions provided by Salesforce. If we were to create a class extending our RestClient, we could make a request as simply as this:

public void makeApiCall(){this.endpoint = 'https://www.google.com?q=foo';this.method = 'GET';this.headers = new Map<String,String>();this.body = '';this.executeRequest();}

This is easier to follow and less verbose, but it's not as fluid and clear as it could be. We can take and abstract this one level further, establishing constructors and methods facilitating development as fluid as get(), patch(), put(), post(), and delete(). Building out these methods becomes a fairly simple matter, for example:

  public void get(){
    this.method = 'GET';
    this.executeRequest();
  }

Of course, this assumes that you've already set the endpoint and other details. Sometimes this is a valid assumption, other times, we may want to have a suite of convenience methods in our rest class that allow us to set the relevant details and execute the request all in one call. Unfortunately, this means that many small methods must be added to our RestClient, like these:

public void get(String endpoint, String body, 
  Map<String,String> headers){
  this.endpoint = endpoint;
  this.body = body;
  this.headers = headers;
  this.get();
}
  
  public void get(String endpoint, Map<String,String> headers){
    this.endpoint = endpoint;
    this.headers = headers;
    this.get();
  }
  
  public void get(String endpoint){
    this.endpoint = endpoint;
    this.get();
  }

Given these methods, any class that extends our RestClient has access to a set of get() methods accepting the full range of options. Thus, get('http://www.google.com'), get('http://www.codefriar.com', mapOfHeaders), and get('http://www.packtpub.com', 'body of request', mapOfHeaders) would all result in a get request being made, and the instance's response object being populated. As an exercise for the reader, you can create similar method sets for Put, Patch, Post, and Delete. Additionally, you can write these messages to automatically encode sObjects into JSON request body strings.

I mentioned earlier that I like to extend my rest class with an API class, and that class with individual request classes. Once a solid RestClient is established, it can be used for any API. However, just about every API I've ever integrated with has had its own quirks, especially around authentication. Additionally, there are some intelligent abstractions to be made on a per-API basis. For instance, when integrating with the Salesforce sObject API, all of our endpoints start with the same URL, that is, https://<instance>.salesforce.com/services/data/version/. The API class can establish a base URL, instance, and version like this:

public virtual class sObjectApiRestClient extends RestClient {

  private final string APIVersion = 'v35.0';
  private final string instance = 'na7';
  private string baseURL = 'https://' + 
instance +
'salesforce.com/services/data/' +
APIVersion + '/';
  private Map<String,String> additionalHeaders = new 
Map<String,String>();

  Public override void executeRequest() {
    //first set our endpoint by prepending it with our baseURL
    this.endpoint = this.baseURL + this.endpoint;
    if(this.request == null){
      this.buildRequest();
    }
    //Having built the request, inject headers for authentication
    injectAdditionalHeaders();
    this.response = this.httpObj.send(request);
    if (response.getStatusCode() > 299) {
      throw new RestClientException('Response Code was: ' +
 response.getStatusCode());
    }
  }

  private void injectAdditionalHeaders(){
    for(String h:additionalHeaders.keySet()){
    this.request.setHeader(h, additionalHeaders.get(h));
    }
  }
}

By overriding the executeRequest method, we get to make changes to the way all calls for this API are made; in this case, setting authentication headers. This way, all calls made by this API class will be made with the authentication headers we need for this API. We can also insert the retry logic and the logic for authenticating if we don't have a valid oAuth token. This override does not change our get(), put(), patch(), post(), and delete() methods. Hence, individual call classes can still use the get() helpers defined in RestClient, even as their executeRequest method is overridden.

When the API-specific class has been defined, it allows us to create classes that make and handle specific calls. For instance, in the sObject API, we have an account endpoint. If we extend our sObjectAPIRestClient class with an sObjectAccountApi class, we have a single place to encapsulate and store all of our account-related REST API calls:

public with sharing class accountSObjectRestClient {

  sObjectApiRestClient restClient;
  private string endpointPath = 'account/';

  public accountSObjectRestClient() {
    restClient = new sObjectApiRestClient();
  }

  public string getAccountById(Id id){
    restClient.get(endpointWithId(id));
    return restClient.responseBody;
  }

  public string updateAccountById(Id id, Account account){
    restClient.endpoint = endpointWithId(id);
    restClient.body = JSON.serialize(account);
    restClient.patch();
    return restClient.responseBody;
  }

  //Helper methods
  private string endpointWithId(Id id){
    return this.endpointPath + id;
  }
}

This is almost perfect! Our AccountSobjectRestClient class now has methods that abstract the implementation of how the API works. Once we've tested this, we'll easily be able to swap out the implementation of this API. There's one more thing we can do to make this usable—handling the translation of the response JSON into objects we can natively use. Writing JSON deserialization code is tedious at best and error prone at worst. Thankfully, a couple of amazing developers developed a web app that will automatically generate JSON deserialization code for you. Simply paste your JSON returned by the API into the box at json2apex.herokuapp.com, and name your deserialization class. When you click on Create Apex, you will download both the deserialization class and the tests for that class (yes, it generates the tests too!), as shown in the following screenshot:

Heavy lifting

Note the Create explicit parse code option. If your JSON requires explicit parsing because it contains, for instance, an Apex reserved word, this web app will automatically convert to using explicit parsing code. Because of this, you can effectively ignore that option. The code generated provides you with a static method called parse, which will return an object of the type you entered in the Name for the generated class field. In our example from the preceding screenshot, we'd receive back an object of the AccountRestResponses type. We can incorporate this into our accountSObjectRestClient class by modifying our methods to return the parsed object like this:

public accountRestResponses getAccountById(Id id){
  restClient.get(endpointWithId(id));
  accountRestResponse parsedObj;
  try{
    parsedObj = accoutRestResponses.parse(restClient.responseBody);
  } catch (JSONException jsone) {
    throw new RestClient.RestClientException(jsone);
  }
  return parsedObj;
}

Now, we have the full round trip package with a generic RestClient class with polymorphic versions for specific APIs that are used by object-specific classes with the ability to serialize and deserialize our data on the fly. With a system like this, you can integrate virtually any RESTFul API available. There are, of course, some limitations we need to be mindful of. The biggest limitation we need to track is the payload size. Asking an API to return 20,000 records may well be within the capabilities of that API, but Salesforce's ability to de-serialize and handle that many records depends on their size. Salesforce has a maximum response size of 6 MB. If your 20k records fit into a response of 6 MB, great! Otherwise, you'll have to request smaller batches of records. You also need to be mindful of the CPU time limit governor. If you can receive 20k records but not process them in time, you're still going to have to ask for smaller chunks. Regardless of its few limitations, the ability to make HTTP callouts to other APIs is an invaluable tool for building integrations between Salesforce and your external APIs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.199.184