As we looked at the overall architecture, it became clear that we were not super happy with it. We were big fans of ViewModel , we thought we could benefit from Kotlin’s coroutines at many different levels, and a more modern pattern like MVVM or MVI would be a better choice than MVP. We then envisioned what our ideal architecture would feature, we identified the areas we needed to chance, and we decided that the best starting point would be the persistence layer.

Cupboard has served Blinkist well for many years, but as new technologies and approaches arise, it started to show some limitations:

It is no longer maintained — the last commit dates back to 2016 😱

It is not inherently reactive

It doesn’t play nicely with some of the recent Architecture Components that Google released

While there wasn’t an immediate need to find an alternative, we felt like we could enable ourselves to using new technologies and to develop at a faster pace, if only we were to find a good alternative.

We evaluated our options, and by now you already know that we decided to go with Room. Its reactiveness fit perfectly with our ideal architecture, its flexibility allowed us to choose between RxJava and coroutines (and potentially LiveData), it comes with a fairly strong type safety, its adoption is now widespread, and chances are that our new hires will be comfortable with using it as well. We considered other alternatives, among which SqlDelight from Square — while potentially more mature and powerful, we didn’t need any of the exclusive features it comes (e.g., multi-platform support).

But first things first, we needed to analyze the current situation and understand what we were dealing with.

Analysis

By analyzing the codebase, we managed to get a sense of how the situation looked like. Our repositories followed roughly the same structure, as we can see in the following diagram for the Library feature:

Data diagram for the Library feature, using Cupboard.

In the process, and by looking at the diagram above, we noticed a few challenges:

Tight coupling between the repository code and the database code (no interfaces)

between the repository code and the database code (no interfaces) Some unit tests were tightly coupled with the database code (i.e., explicit Cupboard calls)

code (i.e., explicit Cupboard calls) Some repositories didn’t have tests at all

We used a seed database to provide an initial set of data for the user and avoid the “empty screen” problem

to provide an initial set of data for the user and avoid the “empty screen” problem Our models were shared across different layers, database, API and presentation (a.k.a. “fat models”)

Too simple of a database schema

The tight coupling was preventing us from easily switching to Room, and at the same time, the absence of tests was making it easier to introduce regressions or to write code that was simply wrong. However, we figured that trying to make the models more lightweight and revisiting the schema would add too much on the plate, so we decided to postpone those to another time.

We saw a potential opportunity. If we made sure that our schema didn’t change during the migration process, we could have simply replaced Cupboard with Room. This was possible, as long as we modelled the Room tables correctly.

Our desired state, if we refer to the previous diagram for the Library feature, would look something like this:

Data diagram for the Library feature, using Room.

This looked both much simpler and much cleaner!