Firebase: The Good, the Bad, and the… Good Again?

by
Tags: , ,

First Contact

We were building a small proof of concept for a mobile app, when one of the stakeholders requested that we evaluate Firebase for it. It was my first exposure to Firebase, which looks like it was a cloud database that grew to include authentication, notifications, and other features.
On the face of it, it looks interesting — mobile devices could load and store data using the Firebase API, no back-end server or hosting needed. Different devices could get events on data changes, so they could easily keep in sync. The database uses JSON as its native format, a good match for a Web or mobile app. And Firebase supports Web, Android, and iOS, meaning native apps plus a Web site or Web-based admin component could all work this way.

And then, a turn for the worse

This first proof of concept was an Ionic app, making it easier to do a sanity check across both Android and iOS. As Ionic uses Angular, and there’s a Firebase/Angular integration package, I took a peek. It was confusing. I expected some convenience API to connect to the database and query it. Instead, I got a convenience API to embed database objects into the page. Huh?

Samples from the AngularFire documentation:

var app = angular.module("sampleApp", ["firebase"]);
app.controller("SampleCtrl", function($scope, $firebaseObject) {
  var ref = firebase.database().ref().child("data");
  // download the data into a local object
  var syncObject = $firebaseObject(ref);
  // synchronize the object with a three-way data binding
  // click on `index.html` above to see it used in the DOM!
  syncObject.$bindTo($scope, "data");
});
<html ng-app="sampleApp">
  <body ng-controller="SampleCtrl">
    <!-- anything typed in here is magically
         saved to our Firebase database! -->
    <input type="text" ng-model="data.text"/>
    <!-- all changes from our Firebase database
         magically appear here! -->
    <h1>You said: {{ data.text }}</h1>
  </body>
</html>

At line 3, I had hoped for something to make Firebase queries more readable, but no.
Instead, at lines 14 and 16, I get HTML widgets “magically” bound to the database, without any intervening logic or handlers. “Three-way data binding” is not what I was looking for.

It turns out that Firebase seems very oriented toward real-time sync. If you type in a text field, the database gets updated automagically. If someone else types in a text field, your screen adjusts. The primary documentation examples and Angular integration seemed focused on this. It’s not what I wanted, though. I’d be fine writing queries and using an event handler to update the database, or receive someone else’s update. Maybe I’d just have to adjust my thinking?
Then I started working with the database more. Ugh. The first advice on the documentation page is to flatten your JSON model for performance. So those nice tree structures or nested objects? Forget it. You need to massage them into separate objects with keyed relationships, just like a traditional RDBMS. What’s the point of using JSON, then?

The proof of concept involved people answering questions, where it’s important to know who answered which questions. Loading the current user might produce a tree-like JavaScript structure such as this:

var person1 = {
  name: 'Aaron',
  email: 'ammulder@chariotsolutions.com',
  answers: [
    {question: 4, answer: 'Lorem ipsum dolor sit amet',
     comments: [...]},
    {question: 6, answer: 'Duis aute irure dolor in',
     comments: [...]},
    {question: 10, answer: 'Excepteur sint occaecat cupidatat',
     comments: [...]}
  ]
};

But Firebase was going to need it all split out into separate objects like this:

var person = {name: 'Aaron',
  email: 'ammulder@chariotsolutions.com'};
var answer22 = {person: 1, question: 4,
                answer: 'Lorem ipsum dolor sit amet'};
var comment67 = {answer: 22, person: 33, text: '...'};

And then the advantage over a relational database is…?

Well, that’s easy enough, we’ll just have the back end restructure the objects on save and load. Only, oops, there is no back end any more. So if you have two native mobile apps and a Web app, you can write that same code three different times in three different languages.
Then it came time to query the database. For my first query, I wanted the results in reverse chronological order. It’s can’t do that. No joke — Firebase can only sort ascending, not descending. They recommend you add some other key with a value of “-1 * timestamp” — in other words, an increasingly negative number — and sort by that, ascending. There’s a “priority” field built in you’re supposed to abuse for this. Really? But there certainly aren’t two priority fields, so you’re on your own past one field of interest.
Speaking of two — you can’t filter by more than one field. As in, your queries can’t have an AND. That’s OK, though, Bill Gates famously said in the 80s that nobody would ever need more than one criterion, right?

Want to check whether a person has answered a question? The answer has both the person ID and the question ID on it. But you can only query by one or the other. Your choice — get all the answers for a question and loop through them looking for the person, or get all the answers for a person and loop through them looking for the question:

firebase.database().ref('answers').orderByChild('person')
    .equalTo(personId).once('value', function(data) {
  var value;
  if(!data.forEach(function(item) {
    value = item.val();
    if(value.question === questionId) {
      // person has answered the question
    }
  }
});

How inefficient is that? So you have to arrange your data like a relational database… only with none of the benefits of a relational database.
Instead, you could put an additional array on each user:

var person = {name: 'Aaron',
  email: 'ammulder@chariotsolutions.com',
  answers: [
    {question: 4, answer: 22},
    {question: 6, answer: 35},
    ...
  ]
};
var answer22 = {person: 1, question: 4,
                answer: 'Lorem ipsum dolor sit amet'};

So now you can avoid the big loop in the query… You just have to accept the worst of both worlds by maintaining both a tree structure and a flat structure and keeping them in sync manually.

Again, there’s a way to hack it together — you can spam out extra columns, so for instance, a user object contains a list of all the posts a user has made and another list of all the comments they have made, and a post has the list of all the comments on it, so if you want to check whether a user has commented on a post, instead of “select comments where user=1 and post=2” you query on just one of those, then use these extra array to manually filter or something. Bottom line, you get to write to three tables on every insert, and put a bunch of logic in where a query criterion should be. Then, since there’s no referential integrity, it’s on you to make sure you don’t break anything.
(Don’t forget you have to write that logic three times in three different languages.)

Redemption

So at this point, I was totally ready to chuck this product and build a REST/MySQL back end, which seemed much less error-prone.
Then we looked at another proof of concept. This one was an Amazon Alexa skill, which was supposed to control content on a TV acting as a computer monitor. As in, you have a browser open on the TV and you say, “Alexa, bring up the news” and suddenly the browser goes to CNN.com.
Normally Alexa apps are fairly stateless, but this one can’t be — it has to connect to a particular browser on a particular monitor and remember which browser goes with which Alexa. It has to know what page you’re on, and when to change it, and etc.
Suddenly, Firebase looks attractive. Alexa can poke some state into Firebase (such as, the URL the browser is supposed to show). The browser can just watch a Firebase record, and get real-time notifications when it’s supposed to change pages.
firebase-diagram
It’s not obvious that we’d go on to build the real app using Firebase, but it’s darn convenient for the PoC — no back-end hosting setup, no fees for developing or doing the demo, etc.
I can also see using the Angular integration if you’re willing to go all-in with Firebase, and attach the database objects directly to your screen widgets and etc. For that to be attractive, I think it needs to be an app where real-time sync is critically important, the data model is not especially complicated, and the multi-platform client is helpful. IRC, or something. Maybe Slack.

Conclusion

Overall, Firebase doesn’t seem like a great fit for the majority of the applications we work on, but I can no longer say I’d never want to use it… so it’s just a matter of making sure the app fits Firebase’s capabilities and constraints. In that regard, I guess it’s not very different from any other tool. 🙂
Pros

  • Keeps two systems synchronized via automatic update notifications — without manual messaging, WebSockets, or etc.
  • No server needed for a simple UI
  • Free hosting

Cons

  • Database is surprisingly limited
  • Supporting Web, iOS, and Android without a back end means rewriting business logic and data transformation multiple times
  • Angular integration only helps if you want HTML widgets tied directly to database updates, with no listeners or handlers in between