Thursday, May 24, 2012

Ruby on Rails, sure, but at some point, you need Java

For the past year I've been working part of my time with Ruby on Rails.  I had heard great things about Ruby on Rails and was excited to get a chance to work with it.

Over the time, I've formed some perspectives about RoR that I thought I'd write down.

First of all, I'm working with an older version of RoR, 2.3.11.  Upgrading to Rails 3 is a Big Project that our team has not been ready/able to take on.  So some of these opinions may be out of date.

My overall first impression - wow, this is really productive.  I can make quick changes to web pages very fast and turn around new features quite quickly.  I really do think that's the strength of Rails.  So I think it's a great choice for an early startup that is quickly trying to get a minimum viable product to market and then start iterating very fast as the company adapts to what it learns.

However, there is a certain point at which the strengths of RoR become weaknesses.

For example, it is easy to rely on Active Record to provide a very simple, fast, easy abstraction to the database.  But when you start scaling up, you can be slammed by the incredibly inefficient ways that ActiveRecord talks to the database.  So you find yourself writing more and more raw SQL to try and process the data faster, and get the performance you need.

Then there's the dynamic nature of the language.  You don't have to mess with the verbose nature of static type definition or time-consuming steps of compilation and deploy.

But the result of that is you end up working without a net.  You just can not tell whether a change you make is going to break something, because you get no warnings from a static compiler telling you you're breaking a contract somewhere.  We have classes with tons of methods that *seem* to be obsolete, but nobody dares to remove them because you never know if some code path, in production, is going to call that method and cause a stack trace.  Even if you search for the signature everywhere, that is no guarantee, because someone could have built up the method call using string concatenation and invoke it using runtime evaluation.

The other problem is the tools just can not reliably do global refactorings like moving classes around or renaming things.  This severely limits your ability to adjust and rethink your application as the design reveals itself more and more or your requirements change.

The argument is that to address this you write lots of unit tests, and that gives you the net you need.  I think unit tests are invaluable, but I really don't like having to basically write a compiler using  unit tests.  I'd rather let the compiler do that for me.  As much as everyone says they are writing unit tests, can you really rely on that?  If you can, well, you must have a collection of very disciplined programmers on your team.  I personally have never seen that...

Finally, the performance of Ruby just can not hold a candle to Java.  Every time we migrate some logic from Ruby to Java, the difference in performance we get is quite astonishing.

So, here are my thoughts about how to go about building a system...

Fine, use Ruby on Rails to whip out your initial product quickly.  But have in your plans moving things over to Java as things stabilize and specific services begin to reveal themselves in your design.   This means, in particular, I would implement a domain layer between the front-end code and the Active Record models.  This allows you to migrate the underlying implementation of the domain layer from Active Record to backend services written in Java.

An alternative that I really like as an approach, but have never had the opportunity to try, is to write only services on the server side, and write all the UI using one of the new client-side Javascript UI frameworks like backbone.js.

Java has the performance, tools, APIs and stability to be the powerhorse of mature, highly scalable, robust middle tier infrastructures.  Ruby really can not hold a candle to it.  Having this kind of infrastructure becomes essential as a company scales up and matures, and customers begin requiring a high quality of service as well as new features.

In general, if you use a domain-driven design, where you focus on building a domain layer that is well thought through, and then provide APIs to the view and third parties, and pluggable interfaces to your lower-level services, you have the foundation for an architecture that can move through the evolution from a quick Ruby on Rails app to a highly robust, scalable system with numerous independent services written in a strong, high-performance language like Java.


Jani Hartikainen said...

I think what you're saying are RoR's downsides are infact downsides of most if not all languages with dynamic typing.

As someone who has worked mostly with dynamic languages, I have ran into pretty much all of this many times.

Test-driven development is a big, big win in dynamic languages especially due to this. Your tests will act as the sort of safety net a compiler would in static languages.

I have also worked with Java, and even with the downsides you list here, I still prefer dynamic languages over Java.

The reasons are simple: Java is such a huge pain in the ass. Java libraries tend to have so many layers and things to everything that even the simplest things become complex to understand.

I have lately been using Haskell for web development. It's statically typed, but it feels nicer to use than Java due to very strong type inference and other features. I think it might be a pretty good alternative which sits somewhere between the heavy enterprise style Java code and the leaner dynamic language code.

Russell Bell said...

I stopped reading at "First of all, I'm working with an older version of RoR, 2.3.11"...

Andy Cohen said...

As Jani says, dynamically-typed languages have their downsides. I'm just learning Ruby now, and I was initially surprised at how little help the IDE was able to give me, because it has no idea which methods are available on any given object.

I also agree with Jani that the Java ecosystem has become unmanageably complex. It gets to the point where you're using so many specialized libraries, each with their own complex configuration, that it becomes very difficult to figure out why something just won't work. I still love the Java language, but because of that complex ecosystem, I'm getting kind of fed up with programming in Java.

Finally, I wonder if you're getting your performance improvements because you're switching to Java, or because you're switching away from a default implementation of Active Record. Couldn't you implement the same data-access layer in Ruby? If you did, I wonder how its performance would compare. That is, it's possible that the performance improvements are coming not from a superior implementation language, but from an improved use of the underlying SQL database.

As always, thanks for your thoughts - this is a topic I've been thinking a lot about lately.

Unknown said...

Maybe I'm an Enterprisey kind of guy, or maybe we're just comfortable with what we know, but I have not experienced Java as a pain in the ass. EJB and Java EE absolutely. Most of my work in Java does not involve working with the horrific frameworks that have been built up around it. Even Spring is a bit much. One reason I like MyBatis is it gives you a nice separation between the SQL and the Java with a minimal amount of frameworky config garbage.

Andy: my experience is that ActiveRecord is a big part of it, but it is also just the raw performance of the VM compared to the Java VM. Although that's probably worth testing out more scientifically than general vague observation.

In terms of TDD - if everyone did TDD assidiously, perhaps you could get by with a dynamic language. But in my experience, that is a Nirvana that is almost never reached within a team (even though I practice it myself), and also comes with its own overhead that you need to account for.

And again, I would argue that a test framework catches things *later* than a compiler that runs *as you type* (Eclipse), and so you have larger write/fix cycles with a dynamic language.

I still find myself so happy when I can safely rename or move things. Even with a TDD test harness protecting you, in Ruby it's just more difficult - a lot of hunt and peck to find all references, or the IDE finds *way* more references than you want (e.g. try renaming something called "execute" or "build"). Ugh.