Salesforce have provided further support for JavaScript in the upcoming Spring’14 release. With a new flavour of the popular Visualforce Remoting facility. Visualforce Remote Objects is a pilot feature I have been trying out in a pre-release org. It’s aim is effectively to make performing database operations, like create, read, update and delete in JavaScript as easy as possible without the need for Apex, and without consuming the orgs daily API limits. This blog introduces the feature and contrasts it with its very much still relevant Visualforce Remoting brother.
Consider a master detail relationship between WorkOrder__c and WorkOrderLineItem__c. The first thing you need to do is declare your intent to access these objects via JavaScript on your Visualforce page with some new tags.
<apex:remoteObjects > <apex:remoteObjectModel name="WorkOrder__c" fields="Id,Name,AccountName__c,Cost__c"/> <apex:remoteObjectModel name="WorkOrderLineItem__c" fields="Id,Name,Description__c,Hours__c,WorkOrder__c"/> </apex:remoteObjects>
The following JavaScript can now be used to access the JavaScript objects the above automatically injects into the page (note that while not shown there is further control over object and field naming, i used the defaults here).
It is an async API, so you provide a function call back to handle the result of the operations (create, update, delete and select are supported), it does not throw exceptions. In the example below if the insert of the Work Order master record is successful the child is then inserted. Note the event parameter actually contains the Id of the inserted record.
function doSomethingJS(answer) { // Create work order var workOrder = new SObjectModel.WorkOrder__c(); workOrder.set('AccountName__c','Hitchhikers.com'); workOrder.set('Cost__c', answer * 100); workOrder.create(function(error, result, event) { // Success? if(error == null) { // Create work order line item var workOrderLineItem = new SObjectModel.WorkOrderLineItem__c(); workOrderLineItem.set('Description__c', 'Answering the question'); workOrderLineItem.set('Hours__c', answer); workOrderLineItem.set('WorkOrder__c', result[0]); workOrderLineItem.create(function(error, result, event) { // Errors? if(error!=null) alert(error); else alert('Success'); } ); return; } // Display error alert(error); } ); }
As per the documentation, this means you no longer need an Apex controller to do this. You can also query using this object model as well, as per this the pre-release documentation querying a Wharehouse object. However before we cast aside our Apex thoughts, lets look at what the above would like implemented via a Remote Action.
function doSomethingApex(answer) { // Create work order and line item via Apex Visualforce.remoting.Manager.invokeAction( '{!$RemoteAction.RemoteObjectDemoController.doSomething}', answer, function(result, event){ alert(event.status ? 'Success' : event.message); }); }
The following Apex code implements the remote action.
@RemoteAction public static void doSomething(Integer answer) { WorkOrder__c workOrder = new WorkOrder__c(); workOrder.AccountName__c = 'Hitchhikers.com'; workOrder.Cost__c = answer * 100; insert workOrder; WorkOrderLineItem__c workOrderLineItem = new WorkOrderLineItem__c(); workOrderLineItem.Description__c = 'Answering the question'; workOrderLineItem.Hours__c = answer; workOrderLineItem.WorkOrder__c = workOrder.Id; insert workOrderLineItem; }
On the face of it, it may appear both accomplish the same thing, but there is a very important and critical architecture difference between them. To help illustrate this i created a page to invoke both of these options.
While also creating a strategically placed Validation Rule on the Work Order Line item, one which would fail the insert if anything other than 42 was entered in the UI.
So given the validation rule in place, lets perform a test.
- Ensure there are no Work Order records present
- Enter an invalid value, say 32, click the buttons, observe the expect error
- Correct the value to 42, click the button again and observe the outcome on the database.
- The expected result is one Work Order record with a single Work Order Line Item record.
Testing the ‘Do Something (Apex)’ Button
After going through the test above, the button initially gives the error as expected…
When the value is corrected and button pressed again, the result is this…
The test passed.
Testing the ‘Do Something (JavaScript)’ Button
Going through the tests again, this button initially gives the error as expected…
When the value is corrected and button pressed again, the result is this…
While Visualforce Remote Objects technically performed as I believe Salesforce intended, this functional tests expectation failed. Since we have two Work Order records, one with no Work Order Lines and one that is what we expected. So why did this additional rogue Work Order record get created, what did we do wrong?
The answer lies in the scope of the database transaction created…
- In the Visualforce Remote @RemoteAction use case the platform automatically wraps a transaction around the whole of the Apex code and rolls back everything if an error occurs, you can read more about this here.
- In the Visualforce Remote Objects use case the database transaction is only around the individual database operations not around the whole JavaScript function. Hence by the time the Work Order Line Item fails to insert the Work Order has already been committed to the database. The user then corrects their mistake and tries again, hence we end up with two Work Orders and not one as the user expected.
Since Apex transaction management is so transparent most of the time (by design), its likely that the same assumption might be made of Remote Objects, however as you can see its not a valid one. Those of you that know core thoughts on patterns will also be thinking something else at this point, that presents a potentially even more compelling reason to be watchful over what logic you implement this way…
Separation of Concerns
As you may have gathered by now if you’ve been reading my blog for the last year, my other passion is Apex Enterprise Patterns. A key foundation of this is Separation of Concerns or SOC for short. SOC sees us layer our code so that aspects such as business logic are located in the same place, easily accessible and reusable (not mention easily testable) from other existing and future parts of our applications, such as API’s, Batch Apex etc.
While the above example is not that complex it illustrates when code in your JavaScript layer might start to become business logic and if so something you should ideally (if not solely for the reason above) consider keeping in your Service Layer and accessing via JavaScript Remoting @RemoteAction.
Summary
Despite this new feature the use of Visualforce Remoting with @RemoteAction Apex should still very much factor in your decision making. And nor despite the issue above should we necessarily let the lack of transaction management count against our use of Visualforce Remote Objects either.
For sure this will become the best and lightest way to perform rapid client side querying without impacting API limits, and I am sure the alias feature (see docs) will be welcome to those preferring more elegant AngularJS or other bindings. All very nice! Furthermore if you are developing client logic that is essentially doing simple record editing then by all means let your Apex Triggers (Domain Layer) do its job and enforce the validity of those records.
Just keep in mind when your starting to write more and more JavaScript code that is orchestrating the updating, inserting or deleting of a set of related records, you really need to be sure be sure you and your testers understand the transaction scope differences between the two approaches described here or switch over to Apex and let the platform manage the transaction scope for you. Finally its worth keeping in mind if you don’t have a client side automated testing strategy your automatically adding manual testing overhead to your backlog.
The full source code for this blog can be found here.
Other Notes and Observations
Here are some final points of note, given this is still pilot hopefully of use to Salesforce as feedback.
- It does buffer requests to the server like Visualforce Remoting, very cool!
- It does not complain when you set the wrong field or get the name wrong, like SObject.put and SObject.get do, maybe to be expected, though since we gave it the field list might have been nice?
- It differs slightly from Dynamic Apex, it uses SObject.put, here WorkOrder__c.set is used
- Success is expressed in ‘error’ being null, not quite what i expected, and different from Visualforce Remoting.
- It does not implement field defaulting
- Errors need to be hanlded by callbacks (as per VF Remoting), though by default errors are not emitted to the JavaScript console like Visualforce Remoting
- References to fields on via the apex:remoteObjectModel fields attribute do not seem to surface as dependencies on the fields, which would be good given how soft references within the JavaScript are.
