Will Ruby Last?
A conversation just struck me the other day. Are people really leaving the Ruby community? Are we really "lead footed" and imploding in a "version hell"?
I'm not going to talk about either of those things, but they made me think. When I first got started with programming I used Flex... and everything was XML... and everyone good used Java. And everything was configurable. The motto:
XML is like violence. If it doesn't solve your problem, you're not using enough of it.
Such a mistake...
Ruby Apps are becoming too Configurable
Now that Rack is so popular, a lot of Rack gems are being pumped out.
And there's a hundred other gems being pumped out every day.
Rack and Rubygems aren't the problems though, neither is the sheer number of them. In fact, the more gems the better in my opinion, because the reality is every piece of functionality, once you get deep in it, should be solved thoroughly and will result in something like a gem. That's a good thing.
The problem is, we are building our gems to be too Configurable. Configuration is punching Convention in the face.
Convention Over Configuration, Take Two
You guys all know about Configuration Over Configuration, it's what made Rails popular. It's what DDH keeps saying. It's on the front page of his blog.
But as Ruby and Rails got more established, and more and more people started depending on it, and more and more people needed their custom "configuration", we developers started catering to them. We started building our gems so that, by default, there are no dependencies. If you want to have "x" feature,
require it. Then all of a sudden this
turns into this
You know it is, you can feel it. Maybe not exactly that code snippet, but like it. There's too many versions and the dependencies are too configurable. If things keep going like this, Ruby will be like Java.
Ruby was a Disruptive Innovation, but like all things that mature, they lose the ability to adapt.
- We have to have more knowledge of the product, which means
- We increase the time spent configuring
- We increase the room for error in managing version conflicts
- We decrease the time spent innovating
- The product requires more time and resources to support, which means
- The product has to grow and grow to handle more configuration options
- The people who invented the project are either stuck there or have to drop it
- The community's growth stagnates, which inevitably means
- The technology will be replaced by a disruptive innovation.
Convention over Configuration: How-to
1. Users should only have to
require one path
Even if your rubygem is packed with functionality and could possibly be divided into smaller 'sub-gems', the end-user should not have to know about that. The gem should be a black box.
Take building an Oauth/OpenID wrapper for example, along the lines of OauthPlugin, AuthlogicConnect, Passport, or OmniAuth, this is something I'm working with lately. Say your gem is called 'magic-auth'. If you wanted to include oauth and openid in a simple Sinatra app, with support for MyOpenID, Google, Facebook, Twitter, and LinkedIn, the trend is to do something like this:
That is, the user must specify exactly what libraries they want that your library uses internally.
Now all of a sudden I have to know about the OpenID gem, about Rack, about Oauth, and about MagicAuth. But I just wanted to Login with Facebook!
On one hand, requiring this configuration complexity is great. It requires you to dig into the code and figure it out, which means you'll learn how great coders code. And it means by the end you'll probably understand the whole Oauth protocol and have written a few tutorials on it. But you'll have shaved a month off your life.
A much simpler solution is to handle that stuff in the background, so the user only has to do this:
Then inside your gem, you solve those gem dependency problems really well:
MagicAuth, you can process
credentials.yml do do exactly what that verbose
use MagicAuth::Twitter chunk did.
Leaving it so we have to manually
require and configure everything for your gem:
- Increases the amount of code I must write to get started
- Increases the depth of knowledge I must have to get started
- Increases the risk for error
- Increases the amount I have to maintain
Instead, encapsulate all the problems and quirks inside your gem and leave it so all your user has to do is
The end result is that the end user, the one who uses your gem, can get started as easy as it was to get started with Rails.
2. Use Interfaces
Define interfaces up front. Use Design Patterns. Create Interface Gems.
- How do you want the gem to be used?
- How do I decrease the barrier to entry and have it require as little knowledge as possible?
Solve these problems right away, they make Test Driven Development exciting. And in the end you have something extremely simple to use.
I'll use Oauth to illustrate. Oauth and OpenID are pretty convoluted, and all of the solutions up until now have been pretty convoluted. Almost every Oauth rubygem has been built for basically 1 service, and there is no common pattern to it. The reason for this is clear, it's not possible without using design patterns.
I do this too, it's a hard habit to get used to. It's hard to remember design patterns' solution to such multifaceted problems. But if we keep this in mind - use design patterns - from the start, we get these benefits:
- Interfaces allow us to solve the same problem for more cases.
- Once we solve the first problem, the subsequent problems are infinitely easier.
So for Oauth + OpenID + BasicAuth, we can boil it down to something like this:
- All of these services act the same if you really think about it, so
- Use the Strategy Pattern to make it so all services have the same Interface.
- Authentication protocols can be boiled down to a
callbackphase (from omniauth)
- And they all seem to boil down to having the same core properties:
secret. So even if Twitter calls it something different than FreshBooks, find some way to make them look the same.
Once we establish an interface, we can easily build more just like it, even before testing.
So first, we make a
Then you write some tests, spend a lot of time making sure you can actually write the next service like that, get it up on Github, and you're golden.
The next one is a piece of cake.
And they both would have this API:
Once that first pattern is put into place, writing tests and extending your gem is easy. And to the end user, they don't need to know "oh I need x version of this dependency, and I need to configure it like y". No, we should solve that problem in our gem. Otherwise, we create a community that's dependent on configuration, which will always lead to over complexity in the long run. And overly dependent systems have a very hard time adapting to change; i.e, they're not Agile.
So to sum up, I'm very excited to be seeing so many great gems being pumped out daily. But in order to choose a fate different that Java, I suggest we make our gems require near-zero configuration. Convention is a core goal, as should be User Experience.
What are your thoughts? Agree, disagree? I'd love to hear your comments.