Wednesday, December 12, 2012

Rails Developers Should Take DCI Seriously

I recently wrote an ebook about Rails. It covers the ways Rails breaks OOP theory, where this creates problems for Rails developers, where it reveals flaws in OOP theory, the core strategies that make Rails so delightful and powerful, and what you can learn from it all to write better code yourself. This involved crystallizing opinions I've formed while hacking Rails apps since late 2005, as well as new research. One thing I learned from writing my ebook: a lot of the strongest OO thinking on how to use Rails converges on the DCI pattern.

The idealistic perspective on DCI is extremely worth considering. DCI stands for Data, Context, and Interaction. Jim Gay's writing a book on the topic called Clean Ruby, and it really does teach an excellent new way to think about OO software. I recommend it very highly.

But there's also a cynical perspective on DCI.

You would expect to hear the cynical perspective on DCI from DCI's critics, but what you get is too weak to qualify. David Heinemeier Hansson, creator of Rails, mocked unnamed object-oriented purists on the 37Signals blog:

Inheritance is always bad, only composition can be our true savior! Only standalone command objects can give us the reusability this application needs!

Turning patterns into articles of faith takes them out of the realm of rational discussion. Code either adheres to the holy patterns or it doesn’t. If it does not, the programmer must first repent, say his 25 rose-delegations, and then swear never to let an opportunity for a policy object extraction pass by him again!

37Signals dev Jeremy Kemper joined in with a snarky tweet:

This argument is simultaneously indisputable and weak. It's a truism that you have to exercise judgement when deciding to use or not use any toolset or mental model for coding. But it's irrelevant because there aren't a whole lot of single-minded OOP zealots in the world of Rails developers. Rails takes significant liberties with OOP, so you can't really build a career on Rails development unless you're flexible about that.

So why did Hansson and Kemper even bother to post this? I was egotistical enough to imagine this served as a response to my book, and/or Clean Ruby. It's almost the argument my book makes; in some of the places where Rails violates traditional OOP rules, the smart thing to do is throw out those rules. (That's some of those places; not all.) But I soon learned the real story. It all came about as a result of some kind of kerfuffle on Twitter between Hansson and the Ruby TDD evangelist Corey Haines.

Rails developers sure love to get their kerfuff on:

For a longtime Rails developer, it looks pretty weird. Where Hansson once employed brilliant, acerbic wit to attack over-engineered failures like J2EE, back in 2005, he's in this case hobbling together a lackluster and irrelevant argument so he can put down a guy who makes Rails easier to understand and use.

The Rails world features a lot of drama, and I've been foolish enough to participate in it myself in the past. The noise factor doesn't make it any easier to hold serious conversations about the best way to use the flawed yet fantastic toolkit which Rails offers developers. (Hint: avoid STI.) It's not so much a situation where the emperor has no clothes as a situation where the emperor will make fun of you for wearing pants.

Camel corduroy pants

But however snarky these very talented and accomplished coders at 37Signals were, they didn't attack DCI head-on. You won't find a lot of substance in the 37Signals post, just nonspecific mockery, as if plausible deniability mattered more in architectural discussions than clarity.

So remember the question of the cynical perspective on DCI? You'd expect to find it here, but ironically, the 37Signals blog post fails to consider the cynical perspective on DCI. It doesn't even mention DCI. If you didn't know the context, you wouldn't be able to make the connection without Kemper's tweet. And this is the downside with Rails drama; it's tiresome, yet if you don't track it, you don't know what the blog posts are really about.

So we have an interesting blind spot. The idealists are not considering the cynical perspective, because they never do, but the critics are not considering it either, because they're too busy hand-waving the entire question away.

So, be warned. Here comes the cynical perspective on DCI. I'm not endorsing it at all, I'm just bringing it to the table: DCI is a bunch of jargon we're forced to resort to because Rails, in a paradoxical twist on the way it creates beautiful code DSLs, has created a mangled nightmare of a DSL when it comes to the actual English vocabulary Rails developers use, when we actually speak to each other with human words.

Specifically, we have obliterated the distance between ActiveRecord, the Ruby gem, and Active Record, the design pattern. Here's the pattern, which Martin Fowler identified, and which Hansson named ActiveRecord (the gem) after:

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

Interesting question: is belongs_to domain logic? If you use ActiveRecord "models" only as data objects, and wrap those in classes which you treat as The Real Models™, are you implementing Active Record?

Prior to the discussion around DCI, many Rails developers began exploring and praising the peace-of-mind benefits which you obtain when you use ActiveRecord as a data object factory, and compose your domain logic in wrapper classes. DCI allows you to put that logic in modules and apply it to your ActiveRecord models as needed; however, in either case, you're putting domain logic somewhere other than ActiveRecord subclasses inside /app/models, and doing so to avoid monolithic Rails apps (or monorails, as they're known). Other approaches use wrapper classes in /app/models or /lib, but with DCI, you take your domain logic out of ActiveRecord classes when writing the code, store it in modules instead, and mix those modules back in to ActiveRecord objects as needed (and only as needed). So when it comes to files on a filesystem, you're not using ActiveRecord to implement Active Record, but when it comes to the action of the system and the objects in memory at runtime, you are.

Remember the pattern's definition?

An object that... adds domain logic [to] data.

So you're going to add domain logic to data. When? Most object-oriented thinking suffers from the legacy of primitive, clunky languages which could not differentiate objects from the classes they instantiated. Thus many ideas which claim to be object-oriented are really only class-oriented, and the naive interpretation of "adding domain logic to data" assumes you could only ever do that at the point in time when a class is defined. But Ruby, with its near-infinite flexibility, can add domain logic to data at any time, and Ruby DCI implementations make use of that.

Even though it has a great pedigree, DCI is an emerging trend among Rails developers. It's not the official solution. Rails officially handles bloated models with a clunkier but not useless concept known as concerns, which live in /app/concerns. DCI is basically just a smarter, better-structured replacement for Rails's haphazard and off-the-cuff concerns idea — kind of like the relationship RSpec has to "pure" Rails testing. The major difference between Rails concerns and DCI modules is that Rails concerns fail to differentiate indirection from abstraction, while DCI modules make that semantic distinction clear.

Zed Shaw wrote the post which I linked to just now, about differentiating indirection and abstraction. I hate to even link to a Zed Shaw post, because he is in my personal opinion a maniac, and it's a verifiable matter of provable fact that he blogged a description of me, Giles Bowkett, engaging in sexual activities with a dog, which was libelous, inaccurate, and, in my personal opinion, also rather disturbing. This is related to my personal opinion regarding his sanity, but I think there's enough of this type of discussion in the Ruby world as is, so rather than explain my conclusions about his mental state — which are probably obvious anyway — let me just say that whatever strange, unwelcome, and surprisingly detailed opinions Shaw might have about my penis, when it comes to the difference between indirection and abstraction, he actually makes sense for a change.

Wag the Dog

It's very important to prioritize abstraction over indirection. It's the difference between re-organizing your desk by creating a system to finish all your tasks — that would be abstraction, and DCI — vs. "re-organizing your desk" by taking everything on the desk, throwing it in a box, and then hiding the box in another room. That would be indirection, and Rails 3 concerns. I would add the caveat that indirection sometimes helps you discover the abstractions you need, but overall, it's a good point.

To recap, DCI uses ActiveRecord to implement Active Record — sort of, in a sense. It avoids creating the gazillion-line User models we all know and loathe, because it puts domain logic in modules instead of on the actual Active Record (or domain-logic-enabled data object), and only mixes that domain logic in to the Active Record when the Active Record operates in a context where it needs such logic. The domain logic lives on the object, but not in the ActiveRecord class file, and only lives on the object when actually needed. Other strategies for de-bloating gigantor User models involve isolating ActiveRecord objects within wrapper classes, and considering those wrapper classes to represent the real domain model — in effect, refusing to use Active Record, the design pattern, while happily choosing ActiveRecord, the library itself, over alternative Ruby ORMs like Sequel or DataMapper.

This means ActiveRecord must logically not be an implementation of Active Record. How can it? If ActiveRecord actually implemented Active Record, it would be impossible to use ActiveRecord without using Active Record. But you can. Not only that, it might be a very good idea.

Instead of claiming ActiveRecord implements Active Record, it would be more accurate to say that ActiveRecord provides a data object which you can use to implement Active Record, if you want.

It makes Hansson's blog post pretty ironic:

Turning patterns into articles of faith takes them out of the realm of rational discussion.

He's describing the mistake that he himself made when he named ActiveRecord after Active Record. Naming gems after design patterns takes those design patterns out of the scope of architectural discussion.

But I'm not just posting this to dick around and win points in an Internet argument. Say you need to discuss this with your teammates on some project, for serious reasons. How do you have that conversation? "We need to use Active Record, but not ActiveRecord." You don't have that conversation, because you don't want to sound like Abbott and Costello. "ActiveRecord?" "No, Active Record!" "Who's on first?" Next somebody starts throwing cream pies and the dancing bear steals your bicycle.

Bears on bikes

Instead, even though The Rails Way (written in 2007) briefly discusses breaking models into modules, and even though James Golick advocated the wrapper classes approach in 2010, and even though Rails 3 provided concerns, the conversation didn't really gel (in my opinion) until people started talking about DCI. That's because DCI provides an interesting way to think about code which makes it easy to solve the problem. Before this, you could hardly even talk about the issue without sounding silly.

So here's the cynical perspective on the cynical perspective: DCI must be bullshit, a crutch, if people are only digging up DCI because Rails uses an inaccurate vocabulary to describe itself. But the idealistic perspective on the cynical perspective is to say that digging up DCI means digging up buried treasure.

Treasure Chest

The guy who created DCI also created MVC, and, as part of a team, he also created Smalltalk. Since Ruby is mostly Smalltalk on a command line, he's almost one of the people who created Ruby. Maybe not a grandparent, but at least a great-uncle.

So "buried treasure" is probably the correct interpretation. Exhibit A: the book Clean Ruby. You should read it.

You should totally read my book, too. The content is just like this, except with greater distance from Rails drama, and a greater insistence on accurate thinking and clarity. I also tell you an interesting story about the unusual life, and sexual problems, of a talking rhinoceros. It's kind of like the infamous Mr. Shaw, but without the terrible social skills, or perhaps _why the lucky stiff meets Hunter S. Thompson.

Here's what people are saying about it on Twitter:

That's right, folks, master poet T.S. Eliot himself commented on my book from beyond the grave. And that's just a sampling; there are many more I didn't post.

Even the creator of Rails has an opinion! Check out what Hansson himself had to say about my book on the 37Signals blog:

If that's not an endorsement, I don't know what is.

(And finally, if you're curious, but not ready to buy, you can download a free excerpt.)