A War on State
3 February 2016, by James Griffiths
Here at Softwire, we’ve been busy building a mobile app for a chain of health clubs, and this is the final post in a four-part series about our experience and what we’ve learned.
We’ve chosen to build the app using Apache Cordova, so our code can easily run on both iOS and Android platforms. Take a look at the first post to read about what Cordova is, the second to see some of the practices we use to make Cordova feel as app-like as possible, and the third to see how we keep our transitions smooth in KnockoutJS.
In the nearly 16 years we’ve been around, we’ve supported many customers’ projects – including maintaining and supporting this customer’s pre-existing web application, which allows users to book exercise classes and tennis courts. All this has given us great insights into what makes software easy or difficult to maintain.
One of the major causes of difficulties in the old website, and elsewhere, is unnecessary state.
As you browse the website, the site builds up a large collection of state about your visit, which it then uses to work out what should be displayed on each page. This means if we find a bug it can be quite time-consuming to work out where each piece of state was originally set, and how the various pieces of state interact.
For the mobile app, we definitely didn’t want to repeat this mistake. We decided to declare war on state and to focus within this project on making the app as stateless as possible, so it was easy to reason about, understand, and maintain in future.
What did this mean for us?
There’s an API for that
To start with, we decided that the app should communicate with the back-end booking system using a stateless RESTful API.
Each API request that the app makes contains all information that the API needs to complete the request, meaning that the API doesn’t need to remember details of any previous requests.
This makes testing significantly easier, as the back-end API can now be tested separately from the app. Debugging is also much easier, as the isolated requests make it clear whether a bug is caused by the app, or the API.
Let’s go on a journey
The design of the UI in our app is reasonably complex; it’s built from hundreds of moving parts, each of which can change depending on the state of the user’s booking.
To reduce the amount of common state that we have to deal with we’ve divided the UI of our app into 3 ‘journeys’, each of 4 separate pages. Each journey is responsible for the flow between various pages, and the interactions between the pages required to make this work. Each page is then responsible for the details of a specific part of that journey.
These are represented by ‘journey’ and ‘page’ view-model objects within our code; any state relating to how the journey and pages are displayed is stored on these objects. Each time the user starts a journey, we create a new instance of the journey and page objects, and when the user leaves a journey, we destroy the journey and page objects. This ensures the journey and pages never contain state left over from a previous visit.
On a strictly need-to-know basis
In the old website a great deal of information was passed between pages, including large, complex object models, dramatically increasing the complexity of the codebase, and making it hard to tell where any information came from.
For the mobile app we have taken the opposite approach and pass around as little information between pages as possible, usually just a single ID, e.g. the ID of the exercise class being booked, or the ID of the booking.
If it breaks, turn it off and on again
Within our app we’re using quite a few fancy UI widgets. One of them – a swipeable carousel of dates – doesn’t play very nicely with Knockout: every time we change the list of available dates, the widget re-draws the carousel vertically, rather than horizontally! Clearly this widget isn’t stateless, sadly, as it’s appearance isn’t just a function of its inputs, but is also affected by its previous state.
We spent a while trying to fix this in the widget itself, but to no avail. Fortunately, we then thought of another approach: instead of changing the list of dates, simple destroy the old widget and create a new instance with the new set of dates, which worked a treat.
This change essentially makes the widget stateless, from our point of view, by destroying and rebuilding it at any time when the state might change, so that the widget’s appearance is instead based only on its current inputs. When statelessness gets difficult frequently and pre-emptively “turning it off and on again” can actually get you remarkably close!