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_archImg 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:

<websync>
  <server providertype="FM.WebSync.Server.Providers.Stateless.SqlProvider">
    <providersettings>
      <add name="connectionStringName" value="ConnectionstringToUse" />
      <add name="manageSchema" value="false" />
    </providersettings>
  </server>
</websync>

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.

Henry Cordes
My thoughts exactly…


Because we think it is very important to serve our users changes and notifications in a real-time fashion, we evaluated a lot of tools, components to achieve this. First we looked at Socket.IO, NowJs and Node.js, but we soon found out that this is not very hard if your team is used to Microsoft tooling. Since the ROBIN frontend (we call it the Agent Desktop) is a web application that is built using the Microsoft stack, we need a framework that is better integrated with the stack we know.

Azure and other considerations

We run ROBIN on Azure which added another complexity. We started out using raw Websockets and used Nugget where we only targeted the newest browsers like Chrome and Firefox, but as soon as the websocket specs evolved Chrome did no longer supported our version and Firefox did not support websockets anymore. So we did an evaluation of real/time Comet frameworks that work with .NET. This was before SignalR existed.

Evaluation
We evaluated Nugget, Pokein, Kaazing and WebSync

The points we evaluated are:

  • features (Websockets, Comet) 
  • client and server framework
  • browsers that are supported
  • maturity
  • learning curve
  • effort
  • price
  • maintainability
  • performance
  • Azure support

We found that while Websync and Pokein did not support websockets at the time, they handled Comet really well. We knew that WebSyncwill support websockets as soon as the specs are official and browsers are supporting these official specs. We found that WebSyncwas more mature, they are robust and their api’s are well documented, although they are more expensive they are most mature also. We found that the google group they use to communicate with their community is very responsive, they will help you as soon as they can.
Whenever we had an issue they helped us and the issue was fixed quickly up to now.

Implementation
We implemented WebSync and are using it in production for a while now, we also have an iPhone app that uses WebSync. This was easy, because they provide iOS examples.

Things to improve
We find that Azure integration works, but can be done better. The configuration of the database (WebSyncneeds four tables in a database) is handled in web.config. When working in azure ServiceConfiguration is preffered, but not supported by WebSync at this moment. So all instances need a configutred web.config and serviceconfigs will not work.
SQL Azure is supported and Azure Table Storage can be used for WebSync storage also, but performance is better on SQL Azure.

Conclusion
All in all, we are very pleased with WebSync, it abstracts the real-time communication away and works really well for us. We hope to grow together with WebSync to a real-time enabled future for ROBIN!

Henry Cordes
My thoughts exactly...