Tuesday, April 15, 2014

Be careful with "the Rails way"

The "the Rails way" expression is becoming popular in the Rails community. Depending on the people who use it, it means either something good or something bad. 

(btw, this blog post has nothing to do with a book with a similar title)

This post is about why I think that "the Rails way" is not always so positive. 

Let me start with the DHH ping pong story, which is happening now as it has a good code example to focus on.

The history

The code ping pong with DHH has already started and my submission was chosen by DHH as the first one.
For those of you who are not familiar with the code ping pong action - there was an interesting discussion on HackerNews triggered by the “Rails - the Missing Parts” post.

At some point DHH wrote:

"If this is a poor example, pick a good example. I'll be happy to code ping pong you whatever example you choose.”

The idea was picked up by Marcin "the awesome" Stecki from Netguru, who created http://www.dhh-ping-pong.com/ and announced it at the http://wrocloverb.com/ conference.

As I’m writing a book on this exact topic (how to turn Rails controllers into service objects) I decided to submit my example. I took the code from my recent blog post on refactoring: http://blog.arkency.com/2014/02/rails-refactoring-the-aha-moments/

DHH picked up the example and here we are, having different solutions to the same Rails problem.

This is the actuall Pong page: http://www.dhh-ping-pong.com/pongs/13 (you can vote there).

Why I’m participating in this?

It wasn't an easy decision. Going public with some piece of code is not unusual in the age of open source. Here, however, it's going to be compared. It's going to be pointed out. There's some voting going on. I'm an easy target now ;) 

It's not about voting, though.

The Rails community is huge nowadays. It's not the same small group of people, as it was in 2004-2005. We have hundreds of thousands Rails programmers in the world. Every day, new people are joining our community.

Rails is no longer used only for small apps. 

It's used in enterprise. People rely on Rails codebases to make money for living. I'm sure, that somewhere out there, there is a Rails codebase, on which people lives depend on.

Let's talk about responsibility.

Are we responsible for all Rails codebases out there? For sure, not. But there's a lot we can do to help our community and thus help the people who rely on Rails codebases.

Can we do something about those codebases being better? Not directly. 

A small thing, that we can do is to trigger discussions. It's not for the sake of discussions. It's for the sake of learning. For the sake of improving. 

It's easy to go too abstract with discussions. This is where DHH is totally right. 

Let's focus on the code. 

I'm glad that DHH picked my example. It's based on a real Redmine code. Such code is written everyday by many programmers. The problem is small enough to be presented in one page. It's big enough to show different approaches.

Can my code be better? For sure! My code wasn't meant to be perfect. The main focus of the research and practice for my book is how to let people get out of the messy Rails codebases on a step-by-step basis. You can't risk rewriting the whole part. Every line of code can be hiding some potential user path. You have to make safe steps. 

I believe in gradual improvement. I prefer evolution over revolution in the code.

That's why my example is not perfect. It's just a snapshot from a refactoring session that I took one evening. However, it's good enough to show some concepts. 

It's showing the concept of a Service object. Even, if at the current shape it's using the "hack" of SimpleDelegator, it's worth to have it extracted.

I'm not proud of this code, neither I'm ashamed.

It's hard to discuss code refactorings without taking the time cost into account. It's not impossible to rewrite Redmine to a better code. It's a function of time. 

If you look at the DHH code, the shape of the code (yes, code has a shape!) is better. Overall, the example is shorter than mine. I think not all the cases are covered, but that's not the point. DHH focused on using some of the Rails features to make the code shorter. The code is good. I'd take it over the existing Redmine code, without any doubt (assuming all the paths were covered!).

It's nice to see both the pieces of code, side by side.

We both agree that the existing code was bad

First of all - that's probably most important here - we both agree that the existing code was bad. That's a good starting point.

Where do we differ?

I didn't actually talk to DHH about it. I hope I'm not misinterpreting his intents here.

The main difference seems to be the fact, that I tried to escape the framework as early as possible. I avoided controller filters. I avoided model callbacks. Those are the things that I teach how to get rid of, if possible. DHH went the opposite way, he's using filters, he's using callbacks.

I'm not here to judge which approach is better.

Let me explain my refactorings here. I want to isolate my code from the framework. I didn't go that far, but I also want to isolate the 'logic' from persistence in the next steps (by using the repository pattern). The goal here is to have your logic framework-independent. To be able to run tests quickly, without hitting the database. To be able to split your app into smaller pieces - gems or microservices. To be able to control the whole application easier, this way.

In a way, it's an unusual approach in the Rails world. It's not so unusual if you look at other communities - Java, .Net, Scala, Clojure, Haskell, even PHP (with the fantastic Symfony2 framework).

Wasn't that the reason that we use Rails not to need to escape from it?

Yes and no.

It's a longer story, for another blog post, probably. In short, I believe that Rails is good for the start (as long, as you're not efficient with other approaches yet), however it's bad for a longer run. 

Rails gives you a nice start, very quickly. This usually helps with the costs and the business side of the project. In the longer run, though - you have a much bigger codebase ( think > 100 database tables, if that means anything useful) and you want to split into smaller pieces. The more you're using Rails-specific features, the harder it is to split. It's a hard, ongoing transformation that can take months or years. That's why it's so important to realise it quickly and start sooner.

I've seen many projects that are stagnating with a typical Rails codebase. It's so hard to escape from the Rails monolith at some point.

You can say, that it's about programmers having low skills and that's the problem, not Rails. It's right to some extent. However, when you're stuck with a Rails codebase, you rely on a lot of magic. The Rails framework code is in fact becoming your code. Whatever happens inside, it's your problem now. It's hard to be a good programmer in the legacy Rails environment.

At the beginning the pain is not so visible. It's hard later, when new requirements are coming. The ones, that are not shown on all Rails tutorials. The ones that require you to actually do some domain modelling, to secure the business logic, to hide some data from different roles. This is the place, where Rails is not helping you - it's going into your way. This is the moment, when you realise that mixing logic and persistence is not really the best idea. This is the moment, when your build already takes 20 minutes and you'd actually prefer to add even more tests. This is the moment, when you're better with a modular design instead of the connected design, as Kent Beck rightly points out.

I'm showing a way that makes it possible to deal with bigger Rails codebases in the longer run. I'm not saying this is the only way to stay alive. It's just one way.

DHH and his team are very successful with "the Rails way". Business-wise I can't even compare my "successes" with their huge success. 

Why choose the way I'm suggesting over "the Rails way"? I believe it's easier. It's based more on the overall programming rules, than on relying on some specific Rails features. You can look at the projects created in other communities and apply the lessons in your project. When you rely on Rails, you're in the ActiveRecord-based programming land. A place that lives somewhere between a relational database and OOP. It's a world on its own. You can be successful here, but it's a one-way ticket.


Brandon Hilkert said...

Great post! I think you both are coming at it from different perspectives. DHH talks a lot about having a great design from the start. And you're approaching this messy codebase and trying to clean it up. I think it makes sense that the process may involve practices/patterns that may not fall in to the "Rails Way". Either way, I think you're write up is fair and I think it's a great discussion to have. Debating code will never be a bad thing. Left to debate theories is where it gets messy, IMO.

Andrzej Krzywda said...

Thanks Brandon!

Yes, that's a good point to make - we're attacking the problem from different perspectives.

Anonymous said...

Lots of good things here, thanks. I like the fact that you lay down your opinions and the reasoning behind.

As someone that transited through several languages, extracting gems from the app & not depending on Rails when it is not needed feels "natural" to me, even if not a common practice in the Rails world.

Agreed also that once you spend too much time getting around the framework, it is probably time to move to another one, but even with the quirks I have with it, Rails (and the related ecosystem) stays a wonderful productivity engine.