Talks

Building in Rails, Backbone, and CoffeeScript

Building in Rails, Backbone, and CoffeeScript

by Derrick Ko

In his talk at the Rocky Mountain Ruby 2012 conference, Derrick Ko discusses the development of real-time web applications using a stack of Rails, Backbone, and CoffeeScript at Kicksend. He emphasizes the importance of user experience, the rise of real-time applications, and best practices for building rich client apps.

Key points include:

- Current Trends: There's a shift towards single-page applications and real-time updates, with growing expectations from users for snappy performance and seamless interaction across devices.

- Kicksend Overview: Kicksend provides a platform for private file sharing across various devices, leveraging a consistent Rails backend with Backbone.js for the frontend.

- Framework Comparison: Rails serves as the API server while Backbone.js is noted for its structure, flexibility, and lightweight framework. Ko highlights their use of Handlebars for logical templating to separate view logic from presentation.

- Real-Time Messaging with XMPP: The talk delves into XMPP’s efficiency for real-time communication, which they utilize for managing user notifications and updates, yielding higher scalability and responsiveness.

- Performance Optimization: Ko explains caching strategies—using Memcached and action caching—to improve response times, alongside techniques for managing perceived performance through optimistic UI updates.

- Best Practices: He stresses maintaining a clear presentation layer for API responses, leveraging tools like Jbuilder, and decoupling models from views to enhance maintainability and reusability in the application.

- Concluding Thoughts: The necessity of simplifying backend architecture with lightweight frameworks and optimizing static asset management is emphasized as crucial for performance. Ko wraps up by encouraging attendees to consider practical implementations of these principles in their own applications.

Overall, Ko's insights present a comprehensive guide on integrating Rails, Backbone, and CoffeeScript to create efficient real-time applications that are user-friendly and high-performing.

00:00:08.820 Alright, hey everyone, I'm Derrick. You can catch me on Twitter at @derecho. Before we start, let me tell you a bit about myself. I've been building web products for over 10 years. I did my first startup in PHP, and after that tanked, I joined the only startup that used .NET, which didn't work out so well. Eventually, I saw the light and joined Pivotal Labs, a consultancy you guys are probably familiar with. After spending some time there, I left at the end of last year and joined Kicksend as their first employee.
00:00:20.800 At Kicksend, I work on product and handle full stack web development. That’s the front office in Mountain View. We’ll talk more about Kicksend in a bit, but first, let’s look at some trends happening on the web right now.
00:00:39.430 User experience is a differentiating factor nowadays, which is evident by the heavy emphasis on design and user interaction. We’ve seen a whole pattern of single-page apps with snappy performance. Users just expect that now. Additionally, there's an increasing incorporation of real-time elements, like updates and notifications, as seen on Facebook or Twitter. These real-time features increase user engagement alongside the overall experience.
00:01:05.379 Products now must consider multiple clients. For consumer apps, you almost certainly need to have offerings on iPhone, Android, and even tablets. The market has responded to these trends with a slew of new frameworks and platforms, such as Meteor and Firebase. These are real-time backends, some even providing front ends. Meteor and Firebase combined have raised over $10 million, which shows how hot this market is right now. Frameworks like Ember and Spine are relatively new and popular JavaScript frameworks that help build rich front-end client apps.
00:02:02.049 So, back to Kicksend— we make it easy to send photos and videos privately from any device to anyone. We have apps across every platform, and we deliver files in real-time. The trends I mentioned also apply to us. Here’s a breakdown of what we use: Rails is still a solid API server for a rich client app. Our web front-end uses the same API that our Android app, desktop app, and iPhone app use. There’s no difference, which keeps things consistent.
00:02:38.590 Backbone.js is the most established web front-end framework out there. It adds structure to your JavaScript code while being lightweight, easily customizable, and flexible. Here’s a rough comparison between the components of Rails and Backbone for people who may not be familiar, showing how templates map to views and routers map to controllers. Models work similarly in both, although Backbone isn't strictly MVC; it’s a framework that adds that structure.
00:03:26.200 We use Handlebars on top of Backbone, which provides logical templates. These compiled templates are very performant on the client side, and since they are logical, they decouple our view logic from the actual presentation layer. We also use XMPP, which stands for Extensible Messaging and Presence Protocol. Why do we use it? For its performance. The server we use is called ejabberd; it’s IO-bound rather than CPU-bound, which really helps us from a scalability perspective.
00:04:04.870 It also uses presence-based delivery, which handles load very well. XMPP is one of the most real-time technologies available. However, you don’t have to use XMPP—there are other services out there. The quickest way to hit the ground running is with Pusher, which runs on WebSockets. Another option is Server-Sent Events, supported in Rails 4 and in most modern browsers. You should definitely check them out!
00:04:41.690 Coffeescript— I know there's a lot of debate about this, but hear me out. Coffeescript is a syntax layer on top of JavaScript. It hides the quirks and nuances of JavaScript, preventing common mistakes. Much like Ruby, it’s fun to write in and focuses on developer productivity. I personally like the idea of an intermediary layer that compiles down to optimized JavaScript, which is particularly useful as JavaScript itself evolves.
00:05:43.200 Let's dive into some concepts and best practices that we use at Kicksend. We'll look at a lot of Rails and Backbone code, but many principles I'll speak of today hold true regardless of the framework you're using. A list in Kicksend helps you quickly send photos to groups of people you care about most. It’s a simple concept: a list is backed by an API call with a simple response that contains a name and a collection of members.
00:06:23.510 When creating API endpoints like this, all your backend best practices still apply; they don’t change. We’re talking about controllers, models, RESTful routes, and this even applies to your views. Your API responses are your views, so you should treat them as such. Thus, you should have a dedicated presentation layer. In other words, avoid using plain JSON to generate your responses. It’s akin to mixing HTML into your models— it’s just bad practice.
00:07:00.330 You should use tools like Jbuilder, which is included in Rails, or Rabble, which is what we use. Having a presentation layer helps you abstract that final response format. For example, if later you decide to change responses from JSON to XML or even to MessagePack, this layer insulates you from those changes.
00:07:51.360 In the example of an API response, we declare model attributes that we want to include in the JSON response that gets sent to the client. Back to our list: when we hit the URL for a specific list, much like the one you see here (list/1/edit), this fragment gets processed as a route through the Backbone router. The Backbone router is very similar to Rails' routes file, where route definitions map to lead to respective handlers.
00:08:23.690 We instantiate a list model with a passed-in ID, which is used to form a REST URL that the model will point to. A Backbone model encapsulates and abstracts all data and interactions of your server-side API. When we call model methods, they map to the corresponding REST calls.
00:09:03.140 A list can have many members, so we set up an instance variable in the model to contain this collection of members. Backbone has no built-in concept of relations between models, so we use a technique that we implement when members get loaded into the list model. A change event is fired when the members are loaded and we bind a load members event handler to it, which loads the members into this collection.
00:09:50.540 Let's take a moment to explain what a collection is. A collection is a simple construct that contains many models which you specify by pointing to the API endpoint. It handles appropriate fetching and initialization of the internal models. A good practice is to have a base collection and base models, much like you have your application controller from which all other controllers inherit. Our collection inherits from KSCollection, which in turn inherits from Backbone's collection.
00:10:51.520 After instantiating a model, we pass it to the view for rendering. We decouple our view from the model itself. Views control the interaction and state of what users see based on the model’s data, which corresponds directly to a DOM element on the page. We keep our views self-contained, knowing the ID of their pivot element, while the templates are used to render them. Keeping your views self-contained enhances reusability, especially in cases of nested views.
00:11:47.980 We’re now ready to render our list view, which relies on a couple of server-side calls to populate. Initially, when loading the page, we render static, non-data-bound sections of the view. In other words, we load the shell of the interface while the data is fetched asynchronously. Once the requests for the list are completed, we fill in the list name on the page. We trigger a renderListInfo method bound to the model’s change event, updating the DOM element with the name of the list.
00:12:39.990 Next, let’s look at populating the view with list members on the left and potential list members on the right, much like your friends list. Once all requests are complete, the members' collection triggers a reset event when they are loaded. We bind this event to the relevant render methods, so the rendered output updates automatically.
00:13:14.100 To recap, on the server side, Rails best practices still apply. We utilize a presenter layer like Rabble as our JSON builder. On the front end, we’ve discussed subclassing Backbone-based objects, keeping views atomic and decoupled, and rendering views progressively as data comes in. Once we’ve rendered a view, the question remains: how do we load it cleanly into a whole page?
00:14:31.620 For Kicksend, we maintain a main container that varies during the app's lifetime based on user navigation. For instance, upon clicking 'inbox', the main HTML container dynamically updates. In a single-page application, we load the view by passing a view manager object into the router. The view manager is responsible for managing the application’s view state. It adds a close method to all Backbone views, which removes content from the page and unbinds all events, performing any view-specific cleanup logic.
00:15:20.200 With the basics of real-time messaging in place, let’s talk about improving performance. First, let’s take a break from our list and look at one of Kicksend's main views: the inbox view, which displays all the photos received. This view is backed by the API deliveries endpoint, which pulls from multiple tables to return JSON. Loading all of this data can be quite expensive, especially since we’re looking at potentially 20 of these at a time.
00:16:07.400 We've managed to maintain an average response time of about 100 milliseconds. How do we achieve that? We cache aggressively. By implementing two levels of caching—using Memcached for traditional object caching and action caching for index methods— we can drastically improve performance and response times. The custom cache key is based on active record attributes, specifically the updated timestamp, allowing us to implement key-based expiration and maintain efficiency.
00:16:57.130 When we have uncachable data, we often wrap it in another Rabble template, which allows us to cache static parts while generating dynamic data as needed. This approach is often referred to as 'Russian doll caching'. For example, when a single sender gets updated, it may cause a cache miss with all linked objects updating accordingly. This method ensures that most other objects remain unaffected, greatly improving cache hit rates.
00:17:37.740 While we’ve optimized for server-side performance, lag remains due to round trips between the client and server. To alleviate this, we focus on perceived performance improvements. For instance, once you trigger adding friends to a list, we immediately update the members' collection and render this change visually before the server call completes. By assuming success, we enhance user experience by making operations feel instantaneous.
00:18:38.250 In a similar vein, we are also greedy about loading data from the outset. When a list is loaded, we fetch not just the targeted list but also anticipated data like friends lists and inbox items. This proactive approach ensures a faster user experience, especially when moving between different app sections. To maintain consistency, we have the local model and the application-wide collection in sync, wherein collection change events update other views accordingly.
00:19:34.640 A quick recap: we optimize server-side performance through aggressive caching and enhance client-side expectations. This results in a very responsive application overall. With the foundation laid, let’s layer on real-time capabilities using XMPP.
00:20:31.880 We start off with our XMPP server of choice, which is KCC Jeopardy, built in Erlang. For the frontend, we utilize Strophe, an XMPP client library in JavaScript to manage connections and interactions. This communicates via BOSH (Bidirectional-streams Over Synchronous HTTP), which is a typical transport layer for XMPP. However, note that long-polling is currently broken on iOS 6 Safari, which could affect compatibility.
00:21:21.530 In order to authenticate with the XMPP service efficiently, we pre-authenticate using a library called Ruby BOSH. This creates a streamlined authenticated connection between our Rails and XMPP servers, thus avoiding the overhead of multiple round trips during the initial authentication phase. The client receives these credentials and uses them to establish a BOSH connection.
00:22:15.790 Our real-time handler class serves as the main entry point for managing real-time events in our application. It establishes the XMPP connection, complete with session credentials, similar to how a Backbone router might function. We attach various event listeners to the types of XMPP messages we wish to handle. For instance, we respond to message stanzas named 'delivery received,' enabling real-time updates in the view.
00:23:21.950 This means that when a message indicating a delivery is received, it instantly renders in our views, making for an immediate response to user actions. The interaction mechanism we’ve created leverages the efficiency of real-time communication whether you’re using XMPP or other real-time services.
00:24:41.870 To conclude, there are numerous ways to enhance your stack’s functionality. Rails can become bloated depending on how it’s utilized, but simplifying back-end elements with lightweight frameworks like Sinatra could offer a more efficient solution. Furthermore, managing static assets effectively by utilizing static site generators like Middleman can drastically improve load speeds. It’s critical to opt for solutions that align best with your project’s goals.
00:26:24.590 Thank you for your time today! I hope you found value in my insights on building a rich, real-time application using Rails, Backbone, and CoffeeScript. I’m now open for any questions you may have.