Real-Time Application Architecture with AngularJS

R. Wolf
Philosophie is Thinking
8 min readJan 28, 2016
http://www.bonkersworld.net/building-software/

One of our recent projects here at Philosophie was building a real-time sports prediction application — WinView Games — which presented a number of interesting and unique opportunities from a programmatic and business standpoint. On the programming side of things, our main challenge was to build an application that could support a robust real-time component sustainably. This article is going to give some background on the project and document our evolving approach to constructing a viable, extensible architecture strategy in our client-side Ionic application to support real-time functionality for WinView. We’ll start off by giving an overview of WinView and our success metrics thus far, dive into the overall software ecosystem of the product, and spend the majority of the article discussing our evolving approach to implementing this real-time functionality in Angular.

WinView is an iOS application in which users are able to place play-by-play predictions on NFL games, as the games are happening, in real-time. Games in WinView are run parallel to real-life football games while a team of professional sports statisticians run a producer console and set propositions, odds, game messages, etc., which appear in real-time on the iOS application. Obviously, given the pace at which football is run, the delays between a producer creating a proposition and users being able to see and place predictions on the proposition have to be essentially instantaneous (our success criteria was 2 second delay or less), as any sort of significant delay would put players at a disadvantage.

The core WinView product consists of three main pieces: a Java-based application API (managed by the excellent team at Ex Machina), a producer/administrator panel built in vanilla Angular (managed by us at Philosophie), and an Ionic client-side application (an Angular/Cordova-based hybrid mobile app, also managed by Philosophie).

We anticipated that this real-time component would be crucial to the overall experience and success of the product, so we as an engineering team decided to set aside some time to be cognizant of application architecture, and not be averse to taking the time to refactor the real-time components of the application when we saw chances for improvement.

We started the project about 8 months ago, it launched 4 months ago, and has overall been a great success. The WinView team has been successfully running games parallel to NFL games for several months, and there are (as of writing this) over 1,000 active players. From the engineering side, we’ve had very few instances of bugs, and almost no issues around the real-time component of the application.

We’ll now go through the client-side application architecture, demonstrating how we evolved our ideas on what constituted working, abstract client-side business logic for the application to process and display data in real-time.

Phase I: The Basics

Phase I Architecture

The initial phase of the project was very basic — our goal was to build out the main pages of the application and to get the page routing/navigation and data retrieval set up so we could start to focus on the business logic as soon as possible while allowing the designers to start building out the frontend design and code.

I personally liked this breadth-first approach to the application, as it allowed us to somewhat visualize how the end result should look while minimizing hinderances to the designers and developers. The architecture here was simple, we were using vanilla Angular code to set up modules, controllers, and views.

For data retrieval, we used the $http service in Angular so the controllers could fetch mock data from the API and dump it onto the page for the designers to style. At this point the application had no real-time components, but I still think this was a smart approach because it served as something of a ‘foundation’ upon which we could continue to build out more complex logic.

Phase II: Abstracting Communication

Phase II Architecture

With the basics out of the way (i.e. users could navigate between pages with static data that was fetched once from the API), we dedicated some time to building the foundation of the business logic that would end up powering the real-time application components.

The first step was to build a single endpoint for interacting with the API. Up to this point we had been fetching data from an unsecured API node. The team at Ex Machina had experience building large-scale real-time applications and we followed their suggestion of moving to a gatekeeper/node-oriented communication system between the client application and the API. Given that the API could one day be dealing with hundreds of thousands of connections (and their other applications already do), they structured their system to intelligently set up client connections based on available ‘gatekeeper’ nodes that sit outside of the actual API layer, directing incoming and outgoing traffic appropriately in a way similar to a load balancer. This ensured that all users would get consistent connection quality. So we built out an APIResource factory client-side that would set up this connection correctly, and we used this factory to correctly format the requests to the API and structure the responses correctly by consumption on the client. Keeping this single point of entry to the API meant that any changes that we’d need to make in the future regarding the request/response cycle would be in only one piece of the application code.

We also spiked the real-time application components in this phase. The API opened up the capability for long-polling connections, meaning that we are able to open a connection and be pushed updates from the server (for instance, a game producer opening a new proposition). In a similar way, we built out a single point of entry to the long-poll connections, an EventService object that would initialize on the start of the application, open up the connection, and listen for new events. This gave us the basic ability for controllers to re-fetch data when needed; essentially, basic real-time.

Phase III: Data-Oriented Service Objects

Phase III Architecture

Up until this point, controllers in our application matched up more or less one-to-one with calls from each API endpoint. For instance, the PropositionsController would only be concerned with the API endpoint that would return an array of all current propositions. However, as the application began to become more nuanced as the business goals evolved, it became clear that multiple controllers, directives, etc. would have to deal with the same data. We were seeing a lot of calls to the API that were more or less redundant.

To keep the codebase DRY as we moved forward, we extracted any logic concerned with data fetching/retrieval/etc. into service objects to match each resource API-side, rather than having each controller be responsible for all the data required to render the view. Each controller was injected with the appropriate service object dependency that matched up to the data it needed, and would be able to retrieve data through the service object, rather than directly querying the API. This decoupled the data logic from controllers; service objects were in charge of fetching and re-fetching data while controllers were only concerned with view-specific logic and calling the appropriate service object when a POST event was necessary (e.g. a user placing a prediction). Service objects cached response data to prevent redundant API calls.

Rather than having our EventService be responsible for deciding which controller should be updated, we decided to have the service simply broadcast on $rootScope each time a new event was pushed, and have controllers listen for controller-specific broadcasts and update/re-fetch their information appropriately.

Phase IV: Reducing Redundancy

Phase IV Architecture

Working with the API team, we realized that our data-oriented service object approach was not ideal, as constantly refetching data with full requests was putting a burden on the API-client communications. We worked with the API team to set up a system in which the long-poll events would also contain the data that would need to be re-fetched, so on a successful long poll event we replaced the data in the service objects with the data in the body of the long-poll.

We fully migrated all of the $rootScope listeners to service objects and implemented get-or-fetch functions on these data-oriented service objects. So on instantiation, controllers would call these get-or-fetch functions on service objects, who would then be in charge of returning the appropriate data. Any new data is provided by long-polling.

Since any data on controller scope was delegated to data cached on appropriate service objects, any time this data was updated on the service object, it would be reflected in the appropriate controller(s) and view(s) as well. This gave the application a true, sustainable real-time component that we use to run WinView games in production today.

Conclusion

https://xkcd.com/292/

Even though we recognized the importance of architecture in the beginning of the project, it quickly became apparent towards the middle of the project how truly vital this was to the application’s sustainability and success. This holds doubly true for the later stages of the project, where we would be adding features and functionality based on actual user feedback gathered by the WinView team. Having a clearly-defined architecture in place made adding large pieces of new functionality a breeze, because there wasn’t any thought that had to go into figuring out how it would fit into the existing application structure. In some ways, it seems like pieces of this structure (mainly where we impose a unidirectional dataflow) are somewhat analogous to parts of Flux, albeit with flux’s Action component being split between initial API calls and our long-polling component.

Basic Flux architecture (https://facebook.github.io/flux/docs/overview.html#content)

While this article is specific to Angular, this pattern can easily be abstracted and implemented in more or less any modern development framework. More and more applications seem to be requiring real-time components and are moving away from the traditional MVC structure, and I would encourage any developers who are about to begin building these solutions to take the upfront time to think about sustainable application architecture — you won’t regret it!

I would also like to thank Peter Gorgenyi for his great work on this architecture design/implementation as well.

Let me know questions/comments/thoughts below!

--

--