OVO's iOS mobile application is - on the surface - very straight forward: a user can log in, submit a meter reading, download their statements and compare their energy usages with national averages. However, the app was built in such a way that we found ourselves with difficulty adding new features, and difficulty debugging customer issues. We'll see how we managed to create a separation between UI and Data...
OVO's iOS mobile application is - on the surface - very straight forward: a user can log in, submit a meter reading, download their statements and compare their energy usages with national averages. However, the app was architected in such a way that we found ourselves with difficulty adding new features, and difficulty debugging customer issues. The core of the application was build on the MVC pattern with a Synchronisation system persisting in CoreData and the lack of separation between Data and Presentation was slowing down the implementation new features
The first step was to create a separation between data and ui. The legacy application had the issue that there was no clear separation between the views and the data layer.
The legacy approach relied on notifications to let the view controller know that data is loaded.
The view controller requests a data model.
The model is not present so the view controller requests the Sync Manager to get it.
The sync manager gets it, saves it in CoreData, and sends a global notification, accessible to all of the app.
The view controller requests the model again.
Confusing? Yes it is.
This solution relies on notifications to get the data, which brings it’s share of new issues. The view controller has to subscribe to notifications when it’s visible and unsubscribe when it’s not. The main problem is that some components can listen to the notification and very quickly it’s hard to know who’s doing what.
We wanted a new approach that satisfy these criteria:
- Easily Testable
- Independent of source
- Single source of truth
- Single Responsibility Principle
- Easily replaceable
We decided to start with an easy solution. With the help of a coordinator (Article about it coming soon), the data is being loaded from a data source and passed to a view controller.
The coordinator is in charge of requesting the data and passing it to the view controller. This creates a very testable application. Each components can be taken as a unit and tested independently.
Now as the application should be working offline we decided to keep the CoreData stack. But we wanted to seclude it from the rest of the application so that in the future we could replace it.
So instead of having a cascading model we went for the composition of data sources.
It is important to note that all the data sources for the same "model" have the same interface:
Let’s take the example of the Meter Reading data sources.
The flow is following
The approach we took was to keep our data source very small and doing only one thing (SRP). We then compose them as one as the have the same interface
So we implemented a "redundant" data source that decides how to get the data; from the api or localy.
In the example, the redundant data source asks the api data source for a model and on success passes it to the local data source that stores it (using core data) and meanwhile passes it to the requesting coordinator. In case of network failure, it will take the model from the local storage.
The coordinator is still talking to one data source and regardless to where the data is coming from (remote or local) passes it to the view controller for display. This simplifies a lot the view controller: it just needs a model, and no need to handle the network or local issues.
Then we wanted to add an in-memory cache to speed up the application. Taking the same approach, the implantation was done in couple of minutes:
In conclusion, having building block data source allows us to have a very small class in charge of a single operation. Composing it allows us to create a more complex persistence system that synchronises the local storage from the api without the application knowing what’s happening. For the application, the data source is doing one thing: Asynchronously getting a model from a source.
Does it answer our requirements:
- Easily Testable:
Yes, because the data source transform from one source to another, we just need to inject the data we want to be processed and check that the output is the one we require
- Independent of source:
Yes The view controller only sees a ‘one true’ model which what the data source decides (only one place)
- Single source of truth:
Yes, because one data source will decide which source it will trust
- Single Responsibility Principle:
Yes the data source is just creating One model from One source.
- Easily replaceable:
Yes all data sources for one resource have the same interface. We could think of replacing the Core Data data source with Realm