[Home](/) | [About](/about) | [All Articles](/all) | [Talks](/talks) | [Github](https://github.com/yigit) | [Twitter](https://twitter.com/yigitboyar) ===== ## [JSONApi is a Blessing For Offline Ready Apps](/jsonapi-is-a-blessing-for-offline-ready-apps) *Published:* 2016/09/04 Recently, I was talking to friend who is working on a new startup. We were talking about how they are planning to handle mobile clients and he mentioned that they are using [JSON API][1]. I remember seeing it before but didn't pay much attention. After talking to my friend, I decided to read more about it and I **loved** it. JSON API is a well defined response format that is targeting to put an end to API response format debates and maximize response caching benefits. It has a bunch of good ideas but what made me love it is the fact that relationships are not nested, rather they are returned as `id` references and the response has a root item which has the array of included (referenced) objects. For example, if you have an API that returns a list of `Books` and each book has its own `Author`, rather than having an `Author` object in the `Book` response JSON, it includes a `Relationship` object in the `Book` JSON that has an `id` field that keeps the `Author`'s id and a `type` that is set to `authors`. Then, there is an additional `included` array in the response that includes the `Author` object. So instead of this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~ { "id" : 4, "name" : "Framework Design Guidelines", "author" : { "id" : 42, "name" : "Brad Abrams" } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Your response JSON looks something like this: ~~~~~~~~~~~~~~~~~~~~~~~~~~~ "data" : { "type" : "books", "id" : "4", "attributes" : { "name" : "Framework Design Guidelines", }, "relationships" : { "author" : { "type" : "authors", "id" : "42" } } }, "included" : [{ "type" : "authors", "id" : "42", "attributes" : { "name" : "Brad Abrams" } }] ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now you might be asking yourself, "So what? What does it have to do with offline ready app architecture?" I helps you solve a great problem, **relations**. The #1 building block for an offline ready app is driving the UI from a persistent model. This means you never* fill your UI from a network request. You never write a code that makes a web service query and updates the UI based on that. Instead, you always build your UI from the model and you have business logic that updates the persistent model based on user's current state (e.g. the page they are visiting etc). When it comes to persistence, relationships are a problem. Relational databases solve this problem with foreign keys and there are many ORMs trying to map it to the application code. This always brings the question, do I load the object light (w/o relationships) or full (w/ relationships). In other words, if we look at the example above, when we load the book, do we load the author field as well? The answer depends on the UI and most ORMs give you the option. They also give you the option to do lazy loading where the relationship will not be loaded until it is accessed (e.g. unless you call `book.getAuthor()`). It works great on the server side, not so much on the client side where *when* you access the data matters a lot. This is like a *mine* planted that will explode eventually. Lets look at an example. Assume you are showing list of books where the author name is not visible. So when developing the page, you use the lazy loading. Months after, PM changes their mind and wants to display author name. Easy, you just call `book.getAuthor().getName()` in the `onBind` method of your RecyclerView. **BOOM** you are querying the database to fetch the author on the UI thread. Worse, you probably won't notice the problem in your tests because it is fast or cached. But not when the database is busy handling a write, which will block all reads (for sqlite). Even w/o that, accessing disk on the main thread should be avoided when possible so even though it is fast 95% of the time, it is still a very bad idea. For instance, if you do reads while Play Store is updating applications on the background, you'll notice the slow down and probably an ANR if the read happens on the main thread. You can easily see this getting more complex when the relationship becomes a list. There is another problem with these relationships. When the same object shows up in multiple lists, the source of truth becomes a problem. Which data is more correct, the Author that showed up in book search query or the one that was returned from the authors list. When user marks an author as favorite, how do you find all copies of that Author object and update it? These are solvable by all means, just a lot of complexity and easy to get it wrong. And side effects will be showing inconsistent data on the UI, confusing the user. Last but not least, there is also the problem of updating the data. What happens if I call `book.setAuthor()` ? Does it go and change the author in the database or just for this book. Even worse, what happens if I call `author.books.add(newBook)`? Does this go ahead and update the database? You don't know, you cannot answer this question by looking at it, so yet another mine planted in the codebase. **So how does JSON API fix this?** It does not, but it guides you to the correct direction: * Data does not come in relationships nested so that you can easily save it to the database as is. Each reference becomes an id reference on the object. * Data does not come in relationships nested so you are incentivized to create a view model. * To support persistence, all you need is a single `saveToModel` method that will get the response and save everything in the `included` array into the database. Each object has its type in the response so this can be an independent code piece that does the work for all responses. Now you are guided to start the right way, you can continue by creating `ViewModel`s for your UI and fill this `ViewModel` from the data that was persisted. For example, for the list of books, it can be something like: ~~~~~~~~~~~~~~~~~~~~~~~~~~~ class BookListViewModel { List books; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now when the PM wants to add author to the results, you change the ViewModel to include the information. ~~~~~~~~~~~~~~~~~~~~~~~~~~~ class BookListViewModel { List books; Map authors; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now it is super easy to observe these because there is no depth. For instance, you can observe the `Author` database for changes and when something is changed, update the `BookListViewModel`'s authors map. You can make it generic such that you can have a mechanism to observe a list of `(ResourceType, String)` tuples and have automagically updating `ViewModel` classes. If you need to get the author of a Book, you write the following code: ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Book book = ...; Author author = viewModel.authors.get(book.getAuthorId()); ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You did it right the first time, you didn't plant any mines and you can scale this code going forward. There is no room for inconsistencies because it is easy to track objects viewed in the UI. Yes, you wrote more code to achieve this but I think the benefits outweigh the cost. Plus, it is an easy code to write and can be automated if desired. *Note that* JSON API is not the first thing that returns results in this format. Indeed in [Path][2], our API was very similar, returning everything as maps. Also, when I was writing the [Architecture Demo][3] for Android Dev Summit 2016, I've used a similar architecture on the API. What JSON API does is to put this mentality into a well defined format that can be adopted in a variety of technologies, which is great. I've also created a [sample parser project][4] on GitHub that parses JSON API responses as separate entity maps. It is just a sample project so no maven url (maybe one day, when i have more time :)). Kudos to [JSON API][5] people. * Never is a strong word, there is always exceptions. [1]: http://jsonapi.org/ [2]: http://path.com [3]: https://github.com/yigit/dev-summit-architecture-demo/blob/master/server/app/controllers/posts_controller.rb#L69 [4]: https://github.com/yigit/jsonapi-parser [5]: http://jsonapi.org/about/ Please enable JavaScript to view the comments powered by Disqus.