Do you remember the Lightning Connect Custom Adapters and MongoDB post?

Things have changed after that post and finally the platform supports all DML statements: insert, update and delete!

To make the magic work I had to make:

Follow the post instructions to setup up your Heroku app.

The core changes are the support for the methods.

Open the MongoDBDataSrouceProvider.cls class:

override global List getCapabilities() {
 List capabilities = new List();
 capabilities.add(DataSource.Capability.ROW_QUERY);
 capabilities.add(DataSource.Capability.SEARCH);
 //new capabilities added
 capabilities.add(DataSource.Capability.ROW_CREATE);
 capabilities.add(DataSource.Capability.ROW_UPDATE);
 capabilities.add(DataSource.Capability.ROW_DELETE);

 return capabilities;
}

The provider has been added with more capabilities, CREATE, UPDATE and DELETE.

Let’s open the MongoDBDataSourceConnectio.cls class and look at the 2 new methods:

global override List<DataSource.UpsertResult> upsertRows(DataSource.UpsertContext context) {
    List<DataSource.UpsertResult> results = new List<DataSource.UpsertResult>();
    List<Map<String, Object>> rows = context.rows;
    Http h = new Http();
    
    for(Integer i = 0; i < rows.size(); i++){
        Map<String,Object> row = rows[i];
        HttpRequest request = new HttpRequest();
        request.setHeader('Content-Type','application/json');
        request.setTimeout(60000);
        Map<String,Object> invoice = new Map<String,Object>();
        if(String.isBlank((String)row.get('ExternalId'))){
            request.setMethod('POST');
            request.setEndpoint(DB_ENDPOINT_NC);
        }else{
            request.setMethod('PUT');
            request.setEndpoint(DB_ENDPOINT_NC+'/'+row.get('ExternalId'));
        }
        
        invoice.put('accountid', row.get('Account'));
        invoice.put('contractid', row.get('Contract'));
        invoice.put('created', row.get('CreatedDate'));
        invoice.put('amount', row.get('Amount'));
        invoice.put('description', row.get('Description'));
        
        request.setBody(JSON.serialize(invoice));
        
        HttpResponse response = h.send(request);
        
        List<Object> mList = (List<Object>)JSON.deserializeUntyped(response.getBody());
        Map<String, Object> m = (Map<String, Object>)mList[0];
        if (response.getStatusCode() == 200){
            String objId = String.valueOf(m.get('_id'));
            if(String.isBlank(objId)){
                objId = String.valueOf(row.get('ExternalId'));
            }
            results.add(DataSource.UpsertResult.success(objId));
        } 
        else {
            results.add(DataSource.UpsertResult.failure(
                String.valueOf(row.get('ExternalId')), 'The callout resulted in an error: ' + response.getStatusCode()+' - '+response.getBody()));
        }
    }
    return results;
}

global override List<DataSource.DeleteResult> deleteRows(DataSource.DeleteContext context) {
    List<DataSource.DeleteResult> results = new List<DataSource.DeleteResult>();
    Http h = new Http();
    
    for (String externalId : context.externalIds){
        HttpRequest request = new HttpRequest();
        request.setHeader('Content-Type','application/json');
        request.setTimeout(60000);

        request.setMethod('DELETE');
        request.setEndpoint(DB_ENDPOINT_NC+'/'+externalId);
        
        HttpResponse response = h.send(request);
        if (response.getStatusCode() == 200
            || response.getStatusCode() == 201){
            results.add(DataSource.DeleteResult.success(String.valueOf(externalId)));
        } 
        else {
            results.add(DataSource.DeleteResult.failure(
                String.valueOf(externalId), 'The callout resulted in an error: ' + response.getStatusCode()+' - '+response.getBody()));
        }
    }
   return results;
}

WARNING: this code is not optimized for bulk upsert/delete because it makes a callout for every record.

It’s a proove of concept, so I challenge you to bulkify the class!

How can you insert an external object provided by a Lightning Connect adapter?

The Database class has been provided with new methods:

  • deleteAsync
  • insertAsync
  • updateAsync

These methods are used to make the calls to the remote system and actually do the work!

Database.insertAsync(new List<MongoDB_Invoice__x>{
    new MongoDB_Invoice__x(Amount__c=1, Description__c ='Async Test 1'),
    new MongoDB_Invoice__x(Amount__c=2, Description__c ='Async Test 2')
});
Database.deleteAsync([Select Id From MongoDB_Invoice__x Where Description__c = 'Async Test 1']);

Every method has an alternative method that provides a callback class, which allows to make further actions after the records are upserted/deleted.

For instance, the asyncUpdate has an optional second parameter of type Database.AsyncSaveCallback that can be created to execute some logic after a specific record is done (the class is called every time a record is updated).

Every asyncDML method returns a List of Database.DeleteResult or Database.SaveResult that contains a link to the asynchrounous operation that can be retrieved by calling the Database.getAsyncLocator(result) method and passing the value to the Database.getAsyncSaveResult(asyncLocator) or Database.getAsyncDeleteResult(asyncLocator): this way you can get the status of the asynchronous operation.