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.

To use real-time features, the platform instance must have access to a message broker.

If you haven’t configured your instance to do so, read this guide to learn how to do it:
Platform configuration

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 (usually http://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…
    • department/
      Document that contains the departments of your company
    • etc…
      Other aspects your company has

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:

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.

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:

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 ); } );

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
You can specify the maxReconnectAttempts (10 attemps), and the reconnectDelay (1000ms) when a connection fails

connect
Connects the service to the platform messaging broker. If the service is already connected, an error will be thrown.

reconnect
Reconnects the service to the Platform broker. If the service is already connected, it will be closed and opened again.

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.