Purpose of the application
I was brought onto the project in May 2015 to build a web application from a set of pre-existing wireframes. I was given complete freedom to pick a tech stack/libraries to use on the front-end as long as:
- I could justify their use and my choice in them
- My choices in them contributed to what the application needed to do
I was going to be building a client-side application that would connect over a single WebSocket connection to a Python backend. The application (including the Python backend) would be deployed onto machines in over 6000 plants
and manufacturing factories around the world, including companies like Foxconn; the manufacturing company that builds electronic devices for a range of companies including Apple, Samsung and Microsoft.
The application was to be used by test engineers to remotely debug scripts they’d written to test physical devices, and by operators who would be using the application to monitor the actual test results and to initiate/abort/pause
the tests themselves. The test engineers could place questions in their script, which would pop up in the application for the operators to interact with, making the test execution dynamic, which the user interface would respond
and update accordingly to. More features of the application are explained below.
Client-side tech stack
Having already built large scale client-side applications for companies like KashFlow and Kayako, I was already experienced in architecting this kind of setup, but this Cisco application needed the added fun of being real-time.
So performance was going to be of paramount importance, as I said, there was going to be tests constantly running and updating the user interface, but it also needed to respond immediately to operator/test engineer intervention.
With KashFlow and Kayako, I’d used Backbone.js to create both applications. However, with the importance of performance, I looked towards React to handle both the rendering of the user interface and responding to the user interaction.
Here’s the email I sent on the 6th May 2015, justifying and explaining the main components in the stack:
I’d say most of that still holds up.
messages sent to and from the backend are encoded and decoded into JSON format, with an example response looking like this:
This tells me on the client-side that a test has started and a container is now running this test.
I would then transmit an event to the rest of the application, informing them that there was new data from the backend.
This event action would look like this (for the update above):
More on the `Dispatcher` object below.
Sending messages to the backend follow the same structure, but the object gets ran through `JSON.stringify` and sent as a string.
I decided that communication throughout the application would largely take places through a central event emitter. For this, I used this excellent library. New data from the backend is broadcast through the Dispatcher (the exported singleton of the Event Emitter library) and whoever is interested in this specific
information will listen out for it.
Backbone is used as the data layer in the application. It’s job is to listen to events from the WebSocket class (through the Dispatcher), parse and store that data. After the Collections and Models have stored this
data, they themselves `emit` an event informing the React Components of this new data.
Once the React Components are informed there is new data available in the Backbone layer, it calls methods on the Collections/Models to fetch this data, ie. `this.props.containersCollection.getContainers()` which
returns an array of objects, which we can just store in the Component State object and trigger a re-render of the Component.
Identifying, analysing, debugging and improving performance in this application was the cause of many a headache and long days of trial and error and staring at Chrome DevTools timeline results and profile outputs.
There were two main areas in the application which required a lot of attention, one being a view of several, even hundreds, of “containers” (a container will run a sequence of tests) that could be started simultaneously.
By running them all at once would cause the backend to be sending multiple updates for each “container” at once, each requiring a visual change in the display of the interface.