The FIRE Stack: Firebase, Interface, REactors
You've probably heard of LAMP and its newer cousin MEAN. Recently, almost by accident, I stumbled into a new way of building web apps that felt almost too good to be true. I'm calling it the FIRE stack.
If you aren't familiar with Firebase, it's a powerful hosted real-time database. Basically it allows you to connect to a datastore using super-simple SDKs and synchronize your data as it changes, no matter how many clients are connected. Don't be fooled: while Firebase can be used as a simple "Backend-as-a-Service" (with impressive results!), its strengths with real-time should make it a serious consideration even for complex applications.
Having a fully real-time data source means thinking differently about how your application works. Instead of using requests and responses to populate a local representation of data, you just work with the data directly and expect that other clients will react accordingly. When you first start building an app, you can accomplish this almost entirely through your front-end interface. I add a message to a chat room and other people connected to the chat room see it populate instantly. Using Firebase's security rules, you can build a surprisingly robust app without needing any back-end processing at all.
What happens, though, when you need that back-end processing? To send out emails, charge credit cards, or pull data from external sources? That's where the reactor comes in.
Reactors are simple worker process (I'm writing them with Node.js, though you don't have to) that act as a kind of microservice layer on top of your Firebase. By listening for internal and external events and updating the Firebase to reflect those events appropriately you can do powerful behind-the-scenes processing that "just works" like any other data in your database.
So now we have Firebase to store and coordinate data, a front-end web Interface for users to access the data, and Reactor processes to do out-of-band work.
Note: I'm intentionally not advocating one front-end framework over another. Firebase has bindings to Angular, Ember, React, Polymer, and more, and you should pick the right fit for your particular team and app. They're all awesome!
Why it Rocks
This pattern is relatively simple, but it comes with some powerful advantages:
- Simple. Your front-ends (whether they're web or native mobile) need to only know how to connect to and use Firebase. Everything else happens behind the scenes.
- Scalable. It's usually your application servers that suffer the most as you start to scale up. With the FIRE stack, you can deploy your web front-end to a CDN-backed hosting provider (hey, like Divshot!) and your reactors don't directly handle traffic and can process asynchronously.
- Composable. Because everything just talks to Firebase, it's very easy to break your reactors down into tiny chunks. You can start with several reactors running in the same Node.js process, but if it makes sense to split some off or write others in other languages, no sweat.
- Real-Time. It's no longer acceptable for modern applications to require a refresh, but building a real-time system from scratch is massively painful. By putting Firebase at the center of your app, all that pain disappears.
- Friendly. Because much of the standard CRUD functionality for an app can be managed using Firebase alone, the FIRE stack is extremely accessible to developers. Front-end developers can often implement new functionality themselves without having to bottleneck on a back-end API being complete.
In Practice
I used the FIRE stack to build the hackathon management app for the 2015 Static Showdown. Since the competition is just something Divshot puts on for fun and for the community, we had very limited time to work on the app.
I used Polymer for my front-end framework and started work using just Firebase. I then layered in a simple reactor to notify us in Flowdock when a new user registered for the competition. It looked like this:
var request = require('request');
var totalUsers = 0;
module.exports = function(top) {
// Listen for new user signups
top.child('users').on('child_added', function(snapshot) {
totalUsers++;
snapshot.ref().transaction(function(user) {
if (!user.registered) {
user.registered = true;
return user;
}
}, function(err, snap, committed) {
if (err){ console.log("ERROR notifying Flowdock."); }
if (committed){
var user = snap.val();
user.pub = user.pub || {};
console.log(user.email, "just joined");
request.post({
json: true,
url: 'https://api.flowdock.com/v1/messages/team_inbox/' + process.env.FLOWDOCK_TOKEN,
body: {
source: 'Showdaemon',
from_address: user.email,
from_name: user.pub.name,
subject: user.pub.name + ' (' + user.email + ') just registered',
content: 'That makes ' + totalUsers + ' signed up to date!'
}
}, function(err, res){ console.log("Notifying Flowdock: ", res.statusCode); });
}
});
});
}
Here top
is a Firebase reference for the application root. I simply listen for a new user to be added to the system, keep a running count, and post about it to Flowdock. The final result looks like this:
In just a few lines of code, I've added new functionality. Better yet, this functionality is completely isolated: no other process or part of the program needs to know about or care about notifying Flowdock.
The FIRE really got cooking, however, when building the live competition dashboard. We wanted it to feel like a community event, so we threw together a dashboard that would let competitors post gifs of their current mood with a live stream of tweets, commits, and deploys from everyone in the competition.
The gif board was driven 100% by front-end code, no reactor needed (thanks to the Giphy API's support of CORS). The streams were individual reactors that used the Twitter Streaming API, GitHub Webhooks, and Divshot Webhooks to populate the feed.
Building an application with the FIRE stack feels very approachable. Each new piece of functionality is just a small, encapsulated layer that knows how to do what it needs to do. Once you start working with it, it becomes quite addicting.
Things to Watch Out For
The biggest thing to watch out for is the potential for client collision. If two or more clients might be working with the same data at the same time, you need to make sure everything goes as you expect (or you'll get double notifications and other less-than-fun things happening).
Firebase's transactions are very important for this. You can see above how the transaction ensures that a notification will only be fired once. It's useful to have some kind of state in Firebase that is updated as reactors process the data. Use onDisconnect to make sure that jobs don't get abandoned halfway through processing.
There's still a wide open field as to building best practices for the FIRE stack. One work-in-progress that I've been hacking on is firebase-rpc, a simple way to provide and consume remote procedure call functionality.
The best tools feel like they get out of the way and let you do your job. When I was building the Showdown app, that's exactly how I felt. While there's still lots more learning and exploration to be had, the way that Firebase, Divshot, Node, and Polymer came together was one of the more enjoyable development experiences I've had.
What do you think about the FIRE stack?