Over the last couple of days, I've started to dig into Firebase, a data storage and data synchronization Backend-as-a-Service (Baas). Like Pusher and PubNub, it can push events over WebSockets. But, it doesn't [necessarily] deal with arbitrary events; rather, it deals with synchronization events that automatically keep local client caches in-sync with the backend data collections.
To get my feet wet, I wanted to start out with a List-Detail style experiment, using Firebase to power the data persistence tier of the demo. But, I didn't want the Firebase implementation to "leak" into the rest of the AngularJS application. As such, the entirety of Firebase is encapsulated within my "FriendService", which exposes data through Promises and scope-driven events.
Getting this all to work was a non-trivial task. It took about 3 days and I'm fairly certain that I'm not using Firebase in the way it was intended to be used. I have tried, more or less, to shoehorn Firebase into a RESTful mindset; meaning, I'm using Firebase to replace the $http service in AngularJS. And, while this works, I get the feeling that it misses the "point" of Firebase and its data synchronization magic.
The biggest hurdle in this experiment was understanding the timing of various events and callbacks. Unlike Deferred values in the Q library ($q in AngularJS), not everything in Firebase is asynchronous. And, those things that are asynchronous aren't necessarily asynchronous all of the time. A lot of the timing depends on the state of the local cache and type of request being made (ex, transactional).
This made it very hard to determine when and if an AngularJS digest needed to be triggered. Since Firebase is not part of the "AngularJS lifecycle", it's up to the developer to tell AngularJS when the View-Model has been (or may have been) updated via Firebase. Of course, since Firebase events are not consistently asynchronous, this leads to all sorts of "digest already in progress" errors. To deal with this, I had to lean heavily on the Scope.$evalAsync() method to safely trigger digests.
NOTE: This actually had a silver lining because $evalAsync() ended up triggering fewer digests than would have been triggered using $apply(); it allowed digests to be chunked around pre-value, "child_added" events.
Once I was able to wrap my head around the Firebase interactions and get those events encapsulated behind AngularJS promises, I was able to get the demo to work:
You may notice that I am explicitly generating the unique ID to be used with each Friend record. To do this, I am using a secondary Firebase reference to a "primary key" value, which my code increments before saving a new Friend. At first, I tried to use the UUID-style IDs that Firebase auto-generates when you push a new value onto a Firebase collection. But, those were hard to use since I had to copy the Firebase ID into the model every time I went to pass data out of the service tier. This worked, but was a hassle and didn't look very attractive. The explicit ID was more upfront work (using a Firebase transaction), but made the rest of the demo feel more natural.
This was a challenging but fun dive into Firebase. So far, I have heard nothing but good things about the service, so I was eager to give it a try. Getting up and running had quite a bit of a learning curve. And, now that I've gotten my feet wet, I'll try to check out other people's code for architectural insights. I think I may have missed the mark on how Firebase was intended to be integrated; but, at least I'm more familiar with the API now.
Nice write up, Firebase gets pretty fun and the security rules are like solving logic puzzles, lol. Have you integrated their JAR with Coldfusion yet?
I am thinking of having Coldfusion push to Firebase and then have our clients get websocket updates from fire base but still hit our REST API for any
Creates, Updates or Deletes. (With fall back to our rest api of course)
Thank you for sharing this information mate... This will help a lot since I am facing some troubles with firebase due to the fact that the view gets a bit of lag when loading information from firebase.
Hello again Ben,
I know this post is kind of old, but I am using your example for a personal project. I have a problem with it, and its about the id you use to retrieve an instance of a "friend" object from firebase. Is that field an internal field that firebase uses internally or is a field you created for this purpose? I am trying to do the same with my "products" table and I am unable to retrieve any data using the id attribute nor a custom field (productId).