Quantcast
Channel: ROBIN » Software development
Viewing all articles
Browse latest Browse all 3

Real-time persistent long-running connections: implementation

$
0
0

In my previous post I wrote about how we (the ROBIN dev-team) decided to use WebSync for our real-time solution. This time around I want to share how we implemented WebSync in ROBIN.

Architecture

WebSync Server supports multiple application architectures.

Integrated application architecture:

Img 01: Integrated Application Architecture WebSync
Img 01: Integrated Application Architecture WebSync

The integrated architecture means that the webapplication and WebSync Server run in the same IIS pool. In Azure this means you can spin up a new instance when the running instance(s) run out of resources.

Isolated Application Architecture:

002_websync_isolated_arch
Img 02: Isolated Application Architecture WebSync

The isolated architecture means that the webapplication and WebSync Server run in separate IIS application pools.
At this moment in time we implemented the integrated architecture, but we are debating if we need to go for the isolated architecture, we think it is more robust and gives us the opportunity to decouple the real-time, or notification logic even more.

ROBIN Deployment Architecture
We have the following deployment architecture:

Img 03: ROBIN Deployment ArchitectureImg 03: ROBIN Deployment Architecture

As image 03 shows we use the integrated architecture, realtime is part of our Process Services layer. In our Frontends, API’s and Background Worker layers we leverage Websync to do the real-time handling for us.

Storage Provider
WebSync needs storage, and supports the following providers out-of-the-box:

  • Sticky Providers
    • In Memory Provider (Default)
    • Sticky Sql Provider
  • Stateless Providers
    • Stateless Sql Provider
    • Azure Table Provider

We use the Stateless Sql Provider, because it is the fastest provider WebSync supports that works on Azure. We already had an on premise working version that used the Sticky SQL Provider, switching seemed the easiest. We think switching to the Azure Table Provider will be easy and we will do this if we need it for scalability reasons, we would like to avoid this because we want the best performance possible.

Implementation details
To use WebSync we had to implement a few parts:

  • Database deployment script (we do not want WebSync to generate the tables it needs in our production environment)
  • Configuration (web.config for the most part, we really want to leverage Azure’s ServiceConfiguration for this, but webSync does not support it at this moment)
  • Server-side publishing
  • Client real-time handling

Database deployment script and configuration
The database deployment was easy, because WebSync can generate the tables for you (default). The only thing we had an issue with, is that if you do not want WebSync generating the tables, but create them yourself, you have to add that in config. you have to use the manageSchema atrribute:


Listing 01: manageSchema = false

Server-side publishing On the server-side we lean heavily on Inversion of Control or Dependency Injection. We find it is real helpfull to compose our objects as we see fit, and it also helps tremendously in our TDD workflow. Because we need to support al kind of clients (webapplications, iPhone, other mobile devices) we evolved to the following (simplified) implementation: IRequestHandlerWrapper

namespace Robin.FrontEnds.Realtime.Interfaces
{
    public interface IRequestHandlerWrapper
    {
        void Publish(long userPersonId, string data, string channelBase);
    }
}

Listing 02: IRequestHandlerWrapper

The IRequestHandlerWrapper is the interface we use to abstract the Publish method that WebSync uses away.

RequestHandlerWrapper

public class RequestHandlerWrapper : IRequestHandlerWrapper
    {
        private const string DashboardChannelFormat = "/{0}/{1}";

        public void Publish(long userPersonId, string data, string channelBase)
        {
            var channel = CreateDashboardChannel(channelBase, userPersonId);
            var publication = new Publication
            {
                Channel = channel,
                DataJson = data
            };

            RequestHandler.Publish(publication);
        }

        private static string CreateDashboardChannel(string channelBase, long userPersonId)
        {
            return string.Format(CultureInfo.InvariantCulture, DashboardChannelFormat, channelBase, userPersonId);
        }
    }

Listing 03: RequestHandlerWrapper

RequestHandlerWrapper implements IRequestHandlerWrapper. Line 14 uses FM.WebSync.Server.RequestHandler and executes the Publish().

Ofcourse, our implementation consists of more, but this, in short, is how we wrap WebSync. We got a RealtimeService (which implements IRealtimeService)and have the notion of ‘Publisher’, for every frontend (webapplication, device) we have a Publisher implementation, we inject these using Dependency injection. It makes us really flexible. For every new Publisher type we only have to create a new Publisher implementation, or leverage an existing one.

Client real-time handling

Our main webapplication has a ‘one page architecture’, we have a single page and use Javascript to show different parts of the application on that page. We use module and jquery widget factory patterns. We are looking into backbone, ember and knockout. We have the notion of a ‘Communicator’ in our clientside architecture. In the Communicator we handle the subscriptions and all incoming real-time (push) messages.

Robin.communicator

robin.communicator = (function ()
{
    var self = this;

    fm.websync.client.init({
        requestUrl: robin.ui.constants.WebSyncRequestUrl
    });

    fm.websync.client.connect({
        onSuccess: function (args)
        {
            self.connected = true;
        }
    });

    addDashboardSubscription = function (agentId)
    {
        fm.websync.client.subscribe
        ({
            channel: '/desktop/' + agentId,
            onReceive: function (args)
            {
                var data;
                var notification = args.data;
                if (notification.NotificationType === robin.ui.constants.ConversationAdded)
                {
                    data = {
                        InboxItems: [notification.InboxItem]
                    };

                    $.publish(robin.ui.buildDashboardTopic(robin.ui.constants.NewInboxItemsTopic), [data]);
                }
                else if (notification.NotificationType === robin.ui.constants.ConversationRead)
                {
                    data = {
                        'ConversationId': notification.ConversationId
                    };
                    $.publish(robin.ui.buildConversationTopic(notification.ConversationId, robin.ui.constants.ReadTopic), [data]);
                }
            }
        });
    };

    removeDashboardSubscription = function (agentId)
    {
        fm.websync.client.unsubscribe
        ({
            channel: '/desktop/' + agentId,
            onSuccess: function (args)
            {
            }
        });
    };

    return {
        addDashboardSubscription: addDashboardSubscription,
        removeDashboardSubscription: removeDashboardSubscription,
        destroy: destroy
    };

})();

Listing 04: robin.communicator

As you can see in listing 04 the communicator also uses a jQuery plugin for pub/sub. This plugin makes it possible to use the publish-subscribe pattern throughout our modules and widgets. This plugin uses topics to differentiate between publications. Whenever a real-time message is pushed from the server, a $.publish(…)event for a particular topic is fired and all subscribers to this topic will recieve the message.

I made the listings as small as possible, to show the intend. The WebSync client api is clean and self explaining. You have to init, than connect and subscribe.

We have lots of advantages from using WebSync, the administration of what clients are connected to which subscriptions is abstracted away for the most part. The way WebSync handles all browsers is a big benefit also, we implemented WebSockets ourselves and had to do all that work manually untill now.

My thoughts exactly…


Viewing all articles
Browse latest Browse all 3

Trending Articles