Real-time capabilities
Introduction
It’s not only important to have access to information, but also to know precisely when information has changed. Carbon LDP™ provides real-time capabilities, which enable you to subscribe to events and to thereby be notified of changes to data. The JavaScript SDK provides convenient access to Carbon’s real-time event notifications.
Subscribing
The real-time capabilities that Carbon provides come in the form of subscriptions to events that occur within the platform. To start subscribing to events with the SDK, first and foremost, you need to know three things: the subject (document) you are interested in, the event you’re going to subscribe to, and the action that will be triggered after the event takes place.
Once you have identified the three of them, you can start writing a subscription with the SDK.
Let’s start this guide by using an example to subscribe to: Imagine that you have a company and you want to know whenever a client is added to your company so that you can perform some adjustments to the company’s budget; you have defined the following structure in your platform instance:
- /
Your platform’s root (usuallyhttp://your-platform-domain:8083/
)- clients/
The document that contains the clients- client-1/
Document defining client 1 properties - client-2/
Document defining client 2 properties - client-n…/
And so on…
- client-1/
- department/
Document that contains the departments of your company - etc…
Other aspects your company has
- clients/
With this in mind, we can start to define our subscription.
The Subject (document)
First, we need to identify the subject we want to subscribe to. In this example, we want to know whenever a client is added to your clients; because the clients/
document contains all the clients as children
, this will be the subject we want to subscribe to.
Now that we’ve identified the subject we want to subscribe to (clients/
), we need to retrieve that document.
let carbonldp;
// ... initialize your CarbonLDP object
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument ) => {
// We've retrieved the subject
console.log( clientsDocument );
}
).catch( error => console.error( error ) );
import { Document } from "carbonldp/Document";
let carbonldp:CarbonLDP;
// ... initialize your CarbonLDP object
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument:Document ) => {
// We've retrieved the subject
console.log( clientsDocument );
}
).catch( error => console.error( error ) );
var carbonldp;
// ... initialize your CarbonLDP object
carbonldp.documents.$get( "clients/" ).then(
function( clientsDocument ) {
// We've retrieved the subject
console.log( clientsDocument );
}
).catch( function( error ) { console.error( error ); } );
With the retrieved subject, now we need to identify the events to which we can subscribe.
The Event
Once we’ve identified and retrieved the subject, we have to specify the event we want to subscribe to. To specify the event, Carbon LDP™ provides a series of predefined events that it can subscribe to; such events are defined in the CarbonLDP/Messaging/Event file.
The list of events we can subscribe to are the following:
- CHILD_CREATED
- Whenever a subject’s child is created
- DOCUMENT_MODIFIED
- Whenever any of a document’s properties are modified
- DOCUMENT_DELETED
- Whenever an existing document is deleted
- MEMBER_ADDED
- Whenever a new member is added to a document
- MEMBER_REMOVED
- Whenever an existing member is removed from a document
To subscribe to any of these events, the documents
endpoint as well as the Document
class, which in this case is our subject, provide three main methods to handle event subscriptions. Such methods are the following:
$on
– Activates a subscription to an event$off
– Deactivates the subscription of an event$one
– A one time only subscription, once it triggers the specified action it will not trigger it again
Along with the methods mentioned above, we can make use of the following ones to avoid having to import the CarbonLDP/Messaging/Event
enum to state the desired event:
Event | Return type |
---|---|
$onChildCreated
|
ChildCreatedEvent
With properties: |
$onDocumentModified
|
DocumentModifiedEvent
With property: |
$onDocumentDeleted
|
DocumentDeletedEvent
With property: |
$onMemberAdded
|
MemberAddedEvent
With properties: |
$onMemberRemoved
|
MemberRemovedEvent
With properties: |
Each one of these methods returns a special object of the type of the event that was triggered. As you can see, every one of them has a property called target
, which is basically a shallow document
of the document originating the event. This property is added to each of the return types because they inherit it from the CarbonLDP/Messaging/EventMessage
As you may have noticed, some methods also return a property called details
. This property gives you more information of the documents produced by the event. For example, in the cases in which the details
properties are of the type DocumentCreatedEventDetails
, they will include a createdDocuments
property, containing an array of shallow documents of the documents that were created. Something similar occurs with the MemberAddedEventDetails
and MemberDeletedEventDetails
, with the difference that they return the shallow documents of the members that were added/removed in the property members
.
Going back to our example, we have identified the clients/
document as the subject, and now we want to know whenever a client is added to the company. Because the clients/
document contains all the clients as its children
and we want to know whenever a client is added, we can portrait this rule by saying: Whenever a client
(child of clients/
) is created (CHILD_CREATED
), we want to adjust the budget of the company.
Putting it all together
With code, this rule would be portrayed as:
let carbonldp;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever the event fails
function handleError( error ) { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument ) => {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
let newClient = {
name: "Nikola",
headquarters: "USA"
};
return clientsDocument.$create( newClient );
}
).then(
( createdClient ) => {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
}
).catch( error => console.error( error ) );
import { Document } from "carbonldp/Document";
import { ChildCreated } from "carbonldp/Messaging";
let carbonldp:CarbonLDP;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event:ChildCreated ):void {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever the event fails
function handleError( error:Error ):void { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument:Document ) => {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
let newClient:Client = {
name: "Nikola",
headquarters: "USA"
};
return clientsDocument.$create( newClient );
}
).then(
( createdClient:Client & Document ) => {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
}
).catch( error => console.error( error ) );
interface Client {
name:string,
headquarters:string
}
var carbonldp;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever the event fails
function handleError( error ) { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
function ( clientsDocument ) {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
var newClient = {
name: "Nikola",
headquarters: "USA"
};
return clientsDocument.$create( newClient );
}
).then(
function ( createdClient ) {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
}
).catch( function( error ) { console.error( error ); } );
And that’s it, we have successfully created two subscriptions to handle events using the Document.
Using the Documents Endpoint
Notice how we managed to create the subscriptions using the Document.
First, we had to retrieve the subject, then identify the events and their respective actions and finally we proceeded to code the subscriptions.
Now, suppose that you want to know whenever a client signs a contract for a new project so that you can update the company’ssales forecast.
To do so, you’ve decided to store every project as a child of a client. If we look at the list of available events, someone could say:
“Because every project will be a child of a client, perhaps I could subscribe to CHILD_CREATED
events for every client” and then proceed to code those subscriptions.
Well, this approach introduces a lot of issues because the number of clients will vary and for every client you’ll also have to create a subscription, which could easily result in a massive amount of requests just to keep the subscriptions alive. This of course is not efficient aside from also being very cumbersome to maintain. Therefore, it is by no means recommended when developing any application.
So, how could we create a subscription that handles that requirement?
If we look at the structure of the example platform, every client is a child of clients/ and because we decided that every project will be stored as a child of client, this means that every project/ will be a grandchild of the clients/ document.
If we look again at the list of events, there isn’t a GRANDCHILD_CREATED
event to subscribe from clients/, so how can we detect whenever a grandchild is created?
Well, there is another powerful way to create subscriptions with the SDK and is by using the documents endpoint.
As mentioned in the events section, the documents endpoint allows us to use the same methods that Document provides to subscribe to events. Nevertheless, the difference when using the documents endpoint is that it also lets us specify an Ant pattern to work with hierarchical subscriptions.
The patterns that can be used are the following:
Ant pattern | Description |
---|---|
* | Every children only |
** | Every children or children of the children and so on… |
Typically, Ant patterns allow us to search for something in a given directory (*) or in any sub-level of a directory (**). However, with Carbon LDP™ they are being used with subscriptions to specify documents to subscribe to. Therefore, the patterns allow us to subscribe to whatever happens in a given document (*) or in any sub-level of that document (**). With that in mind, now we can create the subscriptions to meet the requirement successfully:
import { Event } from "carbonldp/Messaging";
let carbonldp;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever a project of a client is created
function updateSalesForecast( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The Sales forecast has been updated" );
}
// Function to execute whenever the event fails
function handleError( error ) { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument ) => {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
// Subscribing to CHILD_CREATED event of every client
carbonldp.documents.$on( Event.CHILD_CREATED, "clients/*", updateSalesForecast, handleError );
let newClient = {
name: "Nikola",
headquarters: "USA"
};
// Persist the client to trigger the adjustBudget subscription
return clientsDocument.$create( newClient );
}
).then(
( createdClient ) => {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
let project = {
name: "Brand Renovation",
duration: "90 days"
};
// Persist the project to trigger the updateSalesForecast subscription
return createdClient.$create( project );
}
).then(
( createdProject ) => {
// Will print: 1 "The Sales forecast has been updated"
console.log( createdProject.name ); // "Brand Renovation"
console.log( createdProject.duration ); // "90 days"
}
).catch( error => console.error( error ) );
import { Document } from "carbonldp/Document";
import { ChildCreated, Event } from "carbonldp/Messaging";
let carbonldp:CarbonLDP;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event:ChildCreated ):void {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever a project of a client is created
function updateSalesForecast( event:ChildCreated ):void {
console.log( event.details.createdDocuments.length );
console.log( "The Sales forecast has been updated" );
}
// Function to execute whenever the event fails
function handleError( error:Error ):void { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
( clientsDocument:Document ) => {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
// Subscribing to CHILD_CREATED event of every client
carbonldp.documents.$on( Event.CHILD_CREATED, "clients/*", updateSalesForecast, handleError );
let newClient:Client = {
name: "Nikola",
headquarters: "USA"
};
// Persist the client to trigger the adjustBudget subscription
return clientsDocument.$create( newClient );
}
).then(
( createdClient:Client & Document ) => {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
let project:Project = {
name: "Brand Renovation",
duration: "90 days"
};
// Persist the project to trigger the updateSalesForecast subscription
return createdClient.$create( project );
}
).then(
( createdClient:Project & Document ) => {
// Will print: 1 "The Sales forecast has been updated"
console.log( createdClient.name ); // "Brand Renovation"
console.log( createdClient.duration ); // "90 days"
}
).catch( error => console.error( error ) );
interface Client {
name:string,
headquarters:string
}
interface Project {
name:string,
duration:string
}
var carbonldp;
// ... initialize your CarbonLDP object
// Function to execute whenever the event happens
function adjustBudget( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The budget has been updated" );
}
// Function to execute whenever a project of a client is created
function updateSalesForecast( event ) {
console.log( event.details.createdDocuments.length );
console.log( "The Sales forecast has been updated" );
}
// Function to execute whenever the event fails
function handleError( error ) { /* ... */ }
// Retrieve the subject
carbonldp.documents.$get( "clients/" ).then(
function( clientsDocument ) {
// We've retrieved the subject
console.log( clientsDocument );
// Subscribing to CHILD_CREATED event
clientsDocument.$onChildCreated( adjustBudget, handleError );
// It could also be done with clientsDocument.$on( Event.CHILD_CREATED, adjustBudget, handleError );
// Subscribing to CHILD_CREATED event of every client
carbonldp.documents.$on( CarbonLDP.Messaging.Event.CHILD_CREATED, "clients/*", updateSalesForecast, handleError );
var newClient = {
name: "Nikola",
headquarters: "USA"
};
// Persist the client to trigger the adjustBudget subscription
return clientsDocument.$create( newClient );
}
).then(
function( createdClient ) {
// Will print: 1 "The budget has been updated"
console.log( createdClient.name ); // "Nikola"
var project = {
name: "Brand Renovation",
duration: "90 days"
};
// Persist the project to trigger the updateSalesForecast subscription
return createdClient.$create( project );
}
).then(
function( createdProject ) {
// Will print: 1 "The Sales forecast has been updated"
console.log( createdProject.name ); // "Brand Renovation"
console.log( createdProject.duration ); // "90 days"
}
).catch( function( error ) { console.error( error ); } );
Messaging Service
We just saw how to subscribe to an event and execute a function depending on the event we subscribed to.
If you noticed, we didn’t have to configure anything to actively check for events that occurred in the platform, we just had to code our desired subscriptions and that was it. So, how did the SDK know an event happened in the platform?
Well, the platform provides an endpoint which the SDK actively queries to verify whenever a subscription has occurred, that endpoint is http://your-platform-instance:8083/broker/.The class in charge to establish connections to that endpoint is the CarbonLDP.Messaging.MessagingService
, which provides the following methods to handle the connection:
setOptions
connect
reconnect
Whenever you subscribe to an event, if you haven’t called the connect
or reconnect
methods, the SDK will automatically connect to the broker/ endpoint and will actively poll that connection. That gives you the flexibility to just dive into coding the subscriptions without worrying about opening and closing connections.
If you ever want to change the default options -maxReconnectAttempts (10 attempts), -reconnectDelay (1000ms) of the connection, you can do so whenever you want, even if the service is already connected.
Conclusion
The SDK immediately notifies you whenever an event occurs thanks to subscriptions.
A subscription is formed of a subject (document), an event and an action to trigger when the event occurs.
A subscription can be created using the on
method of the Document or the documents endpoint.
The difference between using the Document or the documents endpoint is that the documents endpoint lets you specify Ant patterns to subscribe to multiple levels of an endpoint whereas the Document doesn’t.
The CarbonLDP.Messaging.MessagingService
is the responsible of handling the connections to the broker/ endpoint of the platform and with it, you can configure the maxReconnectAttempts and the reconnectDelay of the active connection to that endpoint.