Recently I was working on an Salesforce app that interacts with a third party api. In our case Sales uses Salesforce to sell a digital product served by a remote server. Unfortunately, the remote API wasn't designed with Salesforce in mind. Complex business processes required multiple api calls. The sheer number of calls needed made direct callouts impractical. To overcome this we built a Heroku based middleware application. We intentionally architected our middleware so a single Salesforce callout could trigger the process. In response to the callout, our middleware application uses the Rest API to call back into Salesforce and get all the sales data. Then it makes many API calls to push that data to the client's proprietary fulfillment platform. To ensure the Salesforce user isn't waiting for a page to load the middleware app works Asynchronously. Unfortunately, this also means we can't show the user any messaging. Success or failure of the individual calls made to the Client's sales platform.
This is where the Streaming API comes into play. Using the streaming API we can show realtime success and error notifications. Salesforce introduced the streaming API a few releases ago. While it has some significant limitations[1] it is one of the most powerful additions to the Salesforce platform. Here's how it works: As a developer, you establish a "Push Topic". PushTopics take the form of a PushTopic object record. PushTopic records have a few key fields; namely:
- Query, which holds a string representation of a Soql query
- notifyForOperationCreate, if true insert dml calls will trigger a push event
- notifyForOperationUpdate, if true update dml calls will trigger a push event
- notifyForOperationDelete, if true delete dml calls will trigger a push event
- notifyForOperationUndelete, if true undelete dml calls will trigger a push event
These fields, are all boolean fields. If set to true, any corresponding DML statement who's data matches your query will result in the API pushing that record. For instance, if you've saved your push topic record with:
notifyForFieldOperationCreate=true
query='SELECT ID, Name, MailingAddress FROM Account'
The push topic will take created records that match and push them to all clients subscribed to that topic.
With our Api integration example we need to make one change to our middleware to enable notifications. Likewise, inside our Salesforce app, we'll need to do two things:
- Establish a push topic.
- Edit our Visualforce page to subscribe to the push topic and display the notifications.
Lets start with the middlware modifications. Our middleware application is returns final results to Salesforce by creating Audit_Log__c records. At this point, it's setup to create an audit log only at the end of the process. If we want to see immediate results, however, we'll need to extend our middleware. The key to an integration like this is to ensure we trigger our push topic. In our case the solution is to create new Salesforce audit logs records with results for each step of the process. Before this change, we wrote one audit log record, now we're writing 7-12 audit logs. Each of these records the action taken, whether it succeeded, and what, if any, error messages were returned.
With our middleware setup to log individual events, we can turn our attention back to Salesforce. First we need to establish a PushTopic record. The easiest way to create a PushTopic is to use the Developer console. Open up the dev console and then click on the Debug menu and choose "Open Anonymous Apex" window. This anonymous apex window allows us to execute small bits of code without having to generate a full class. Copy and Paste this code sample to your Anonymous Apex window:
PushTopic pushTopic = new PushTopic();
pushTopic.Name = 'ExternalAPINotifications';
pushTopic.Query = 'SELECT Id, Name, Action__c FROM API_Audit_Log__c';
pushTopic.ApiVersion = 30.0;
pushTopic.NotifyForOperationCreate = true;
pushTopic.NotifyForOperationUpdate = false;
pushTopic.NotifyForOperationUndelete = false;
pushTopic.NotifyForOperationDelete = false;
pushTopic.NotifyForFields = 'Referenced';
insert pushTopic;
Click execute, and your anonymous apex window should disappear. If you see a Success message in the log window, move on!
Within our Visualforce page, we have a bit more work to do. Essentially, we need to incorporate a few Javascript libraries and display the results. To do this, we'll need to:
- create a Static resource bundle
- load a few javascript files on our visualforce page
- add some markup to display
- write a javascript callback
- add a filter
While Salesforce handles the work of the streaming API; to display data from the api we'll need to subscribe to our pushTopic. To subscribe we use the cometd javascript library. CometD is a javascript implementation of the Bayeux protocol, which the streaming API uses. Using this library, along with jQuery and a helper library for JSON we can subscribe with a single line of code.
$.cometd.subscribe('/topic/ExternalAPINotifications', function(message) {...}
But lets not get ahead of ourselves. First, lets create a static resource. Static resources are created by uploading zip files to Salesforce. For more information see this helpful document: http://www.salesforce.com/us/developer/docs/pages/Content/pages_resources_create.htm I've created a helpful zipfile containing all the libraries you'll need to use the Streaming api here: https://www.dropbox.com/s/4r6hwtr3xvpyp6z/StreamingApi.resource.zip Once you've uploaded that static resource, open up your Visualforce page, and add these lines at the top:
<!-- Streaming API Libraries -->
<apex:includeScript value="{!URLFOR($Resource.StreamingApi, '/cometd/jquery-1.5.1.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.StreamingApi, '/cometd/cometd.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.StreamingApi, '/cometd/json2.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.StreamingApi, '/cometd/jquery.cometd.js')}"/>
These lines tell Visualforce to include the javascript you need on your page.
The streaming API will add HTML blocks to our page whenever the API fires a PushTopic. We'll need to put a div on our page to let the API know where to put our streamed data. This is largely up to you, the developer, but I tend to try and keep my messaging at the top of the page. This is inline with how Salesforce does their own validation messaging etc. Wherever you decide to put it, put a div tag, and give it the id of "apiMessages"[2] Something like this will do nicely:
<div id="apiMessages"></div>
Then at the bottom of your page's markup, find the ending </apex:page>
tag. Just above that tag, place a new script tag block like this:
<script type="text/javascript">
</script>
Inside this script block, we're going to subscribe to our pushTopic and setup how our data looks when presented. To start, lets create a jQuery on document ready handler like this:
<script type="text/javascript">
(function($){
$(document).ready(function() {
// We can do stuff here.
});
})(jQuery);
</script>
Code inside this block will run when the browser signals that the document is ready. It's in here that we want to initialize our cometd connection to the Streaming API and do something with our data. The CometD library is implemented as a callback system. We'll write a callback function that outputs our data to the screen. But first, let's hook up Cometd to the Streaming API.
<script type="text/javascript">
(function($){
$(document).ready(function() {
$.cometd.init({
url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
});
});
})(jQuery);
</script>
A couple of important notes here. The url for and request headers are identical, regardless of org. Astute observers will note that we're letting Visualforce substitute in actual API session credentials. This means that the Streaming API is following Salesforce security. If you can't see the streamed object normally, you won't be able to see it here.
Once we've setup the connection, we can establish the subscription. As before, it's a simple one-liner addition to our code.
<script type="text/javascript">
(function($){
$(document).ready(function() {
$.cometd.init({
url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
});
$.cometd.subscribe('/topic/ExternalAPINotifications', function(message) {...});
});
})(jQuery);
</script>
The subscribe method accepts two parameters. The first is the text representation of the stream to subscribe to. It's always to going to be '/topic/<>'. The second is a callback function to be executed whenever data is received.
In our example above, we're creating an anonymous function that accepts a single argument - message. message is a javascript object an id available to the body of our function. Within this function you can do anything that Javascript allows, from alert(); calls to appending objects to the Dom tree. Functionally, appending elements to the dom is the most practical so lets build that out. Remeber the div we created a few steps back? The one with the Id "apiMessages"? Lets put that to good work.
<script type="text/javascript">
(function($){
$(document).ready(function() {
$.cometd.init({
url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
});
$.cometd.subscribe('/topic/ExternalAPINotifications', function(message) {
$('#apiMessages').append('<p>Notification: ' +
'Record name: ' + JSON.stringify(message.data.sobject.Name) +
'<br>' + 'ID: ' + JSON.stringify(message.data.sobject.Id) +
'<br>' + 'Event type: ' + JSON.stringify(message.data.event.type)+
'<br>' + 'Created: ' + JSON.stringify(message.data.event.createdDate) +
'</p>');
});
});
})(jQuery);
</script>
Lets unpack that a bit. To start with, we're invoking jQuery via $ to find the element with Id "apiMessages". We're asking jquery to append the following string to the apiMessages div for every record it recieves. Thus, as records come in via the streaming api, a paragraph tag is added to the apiMessages div containing the text block "Record Name: name of record" <br>
"Id: id of record" <br>
... and so forth. It's this append method that allows us to display the notifications that are streamed to the page.
At this point we have a functional streaming api implementation that will display every streaming record that matches our PushTopic. This can add a bunch of noise to the page as we probably only care about records related to the object we're viewing. There are two ways to acomplish this kind of filtering. The first is to adjust our subscription. When we subscribe to the topic we can append a filter to our topic name like this:
$.cometd.subscribe('/topic/ExternalAPINotifications?Company=='Acme'', function(message) {...});
In this situation, only records matching the push topic criteria AND who's company name is Acme would be streamed to our page. That said, you can filter on any field on the record. For more complex filtering, you can filter on the messages data itself. Because you're writing the callback function you can always do nothing if you determine that the record you recieved isn't one you wish to display.
- [1]: for instance, you can only have 20 active push topics. Additionally, the Streaming api isn't available on all standard objects, just most of the standard objects and all of your custom objects.
- [2]: You can actually give it any id you'd like, but remember it, as you'll need that id later.