Over the past few months I’ve learned an immense amount from the all new Basecamp codebase. This year for RailsConf, I was able to show off some new (and old!) patterns in the codebase along with some neat libraries that we’ve put to serious use for our customers. The slides are over at SpeakerDeck if you’d like to see them all, and hopefully a video of the presentation will be up soon!
Here’s a few choice sections from the talk, and plenty of great links to check out in the meantime:
We’ve talked in bits and pieces about the infrastructure behind our apps, and I wanted to give a clearer picture of what Basecamp looks like both on our production machines and on each developer’s box.
For databases, we hook up to several:
- MySQL: Basecamp’s best friend. Stores pretty much all application data.
- Memcached: Used for our key-based caching system. Our 1 TB cluster just started evicting keys last week!
- Redis: Used mostly for Resque, and for tracking live page updates with sorted sets.
- Elastic Search: Fantastic, open source searching.
There’s a mix of internal services as well, which are hooked up to through a series of gems and Rails engines:
- Depot: Where your files are stored. I’m sure this is an old rails/train pun.
- Portfolio: In charge of displaying and resizing avatars across all of the services.
- Launchpad: Our single-sign-on gateway. Handles all authentication concerns.
- Queenbee: Where the honey’s made! This is our internal billing system that manages plans, accounts, trials, and more.
Thankfully, we don’t need to set up each individual service on our own machine to get hacking on Basecamp. Two are required though, along with the main Rails app: Portfolio, and Launchpad. The app would look pretty silly without avatars, and even locally we need to sign in to get access.
Running these services side by side is usually a challenge, but Pow handles it like a champ. If you haven’t checked it out yet, it’s a wonderful Ruby web server that hooks into OSX internals to make serving apps locally painless.
We also have a convention across all of our apps to use a single bash script that makes getting started with development so much easier. This script lives at
script/setup in each repo. Typically, getting an app working on your machine feels like blowing on an NES cartridge. Having one script to be a “reset button” for your app that sets up the database, bounces your local web server, and does whatever custom wrangling required to get cranking away can save an immense amount of time and frustration for your team.
ActiveSupport’s Concerns are used extensively in our models and controllers. Using them in your Rails app enables two big wins: it’s the simplest refactor out there, an Extract Method, and it allows you to group alike methods together instead of each class being a grab bag of unrelated functions. Here’s an example of our Ajax concern, used across several controllers:
# app/controllers/concerns/ajax.rb module Ajax extend ActiveSupport::Concern included do before_filter :set_ajax_cookies end private def set_ajax_cookies # set some cookies! end end
It’s a standard Ruby module, but the
included block that allows class-level code to be be easily added in. Of course, even if you extract everything into concerns, your class can still be a ball of mud. Please don’t think this is an excuse to gloss over proper design for your code.
The Ruby to CoffeeScript ratio is around 1:1 as well. Since Rails 3.0, using HTML5 data-* attributes for decoupling behavior between the client and server side has been immensely helpful. We’ve taken that to the next level with our usage of
data-behavior. Here’s an example of what hooking up the date picker for todos looks like:
<label> <b>Set the due date:</b> <div data-behavior="date_picker"></div> </label>
$('[data-behavior~=date_picker]').live 'focus', -> $(this).datepicker()
~= as well, which allows us to chain multiple behaviors on the same element. If you’d like to get a start on using these in your app, rails-behaviors has some great examples.
Lots of Logging
Anything we can do to make debugging customer problems easier and faster is worth it. Noah blogged earlier this week about how we use marginalia to log the controller and action that each SQL query came from.
We also tag each log line in production with a unique ID for each request and the account ID of the user. This has two big benefits: we can easily
grep out one entire request with the unique ID, and we split logs based on the account ID to narrow down the search.
Accomplishing this in Rails 3.2 is easy, thanks to ActiveSupport::TaggedLogging. Here’s a simple example of getting it running, crack open
YourApp::Application.configure do config.log_tags = [ :uuid ] end
After bouncing your server, you’ll start to see a unique ID for each request in the log:
[71ba53fd717c67a6677a058f4a5acdf4] Processing by ProjectsController#index as HTML
config.log_tags array can also take a static string, or a proc/lambda to allow for more customization. I’ve pulled out some more examples to check out as well.
There’s a lot more to the WebKit console than
Two examples are
console.groupEnd(). Warn adds the yellow icon, and grouping allows you to collapse/expand similar log messages:
These are only enabled when in development mode, but it’s really simplified how we dealing with log messages and nailing down performance issues.
Another great example is
console.profileEnd(). There’s an entire tab for performance tuning and timing on the WebKit console. How did I miss this!?
This tool has been really useful for simply visualizing what’s slow. It’s a bit obtuse at times, and the nesting of how functions are called/displayed is a little wacky, but definitely check it out.
Finally, Chrome has a little piece of code that can show you how many milliseconds it’s been since the page load:
chrome.csi().pageT // 2933006.221
This feels like it should be standardized somehow! In the meantime there’s a small snippet you can toss in to get it working in any browser.
Hopefully you’ve picked up something new so far! There’s plenty more on the slides, along with some notes that conference-goers took. One huge take-away I’ve gotten from the Basecamp code base is that we’re still evolving and learning, and there’s no reason to stop!