Evaluation: moving from Java to Ruby on Rails for the CenterNet rewrite

Preface

This document was written by me (Rick Bradley) as part of our IS Applications group’s process of (re)evaluating platforms for deployment of a large-scale healthcare application (“the CenterNet rewrite”—discussed on the Rails mailing list). This evaluation document was prepared in September of 2005 and is therefore already out of date technologically—a number of the perceived shortcomings of Ruby On Rails have been eliminated, alleviated, or mitigated. For instance, we found to our surprise that it was trivially simple to deploy Rails on Windows and to automate build and testing.

Later additions to the document will be in highlighted type. Feel free to redistribute this document freely in whole or in part.

Introduction

The Ruby on Rails framework is an open-source web application development framework developed initially by David Heinemeier Hansson at 37signals in Chicago while working on the BaseCamp productivity application. The framework uses the popular Model-View-Controller paradigm for separation of concerns in application development. Ruby on Rails is written in the highly-dynamic open-source object-oriented Ruby programming language, developed in the early 1990’s in Japan.

Benefits

There are a number of notable upsides to moving CenterNet development from our Java development stack to the Ruby on Rails framework:

Vastly reduced code footprint: We have read our Fred Brooks and respect the fact that there has never been a “Silver Bullet”, and there is unlikely to ever be such. Rails is not a Silver Bullet. However, widely reported results place productivity increases over modern Java methodologies (e.g., J2EE , Struts, etc.) in the 6-fold to 10-fold range (with many of these claims coming from long-time Java luminaries). Preliminary tests by our Technical Lead put the code reduction for a normal module in our Java stack converted to Rails at roughly 20:1. Rails uses the dynamicity of Ruby to extreme advantage and produces 4GL-like effects without resorting to code generation techniques (which suffer from inflexibility post-generation). The reported productivity increases point as well to underreported problems in the industry: web development as a whole likely lags application development productivity significantly due to the relative youth of the paradigm (stateless, distributed, thin-client applications); and the Java development model for web applications is additionally verbose and needlessly complex. Arguably, Ruby derives much of its productivity benefits from its SmallTalk heritage, and many would view Rails’ productivity to be bounded from above by the productivity of SmallTalk. The Rails paradigm has inspired Rails-alike projects in nearly every modern web development language. Rails appears to remain more productive than even the most productive of these Rails-alikes, probably primarily due to Ruby’s suitability for speedy development in this one particular domain of web application development. In short, Rails leverages Ruby to bring web application development back closer to the productivity of productive non-web application development frameworks. zero configuration: Rails applications require a few lines of specification to identify each database connection you will use, as well as a line of configuration for each different type of routing you will use (typically 3-4 lines for an entire application). Both of these are necessary in any environment. By contrast, rather than the 20 or so lines required in a Rails application, typical medium-scale Java applications require thousands of lines of XML configuration, many of which are strongly-coupled across application layers. DRY principle: Simply put, “Don’t Repeat Yourself”. Rails embraces the DRY principle and strives to maintain orthogonality among concerns. This reduces coupling, which reduces maintenance cost (and increases maintenance and development productivity), and eliminates the “shotgun code smell” which pervades modern Java web development: when a change is made at one layer (for example, adding a column to a database table) changes propagate through code in other layers. A problem which had long been perceived as a shortcoming of the “model-view-controller (MVC)” architecture, shotgun code smell is exposed by Ruby on Rails to have instead been a problem with the language (Java) and frameworks in use to implement MVC . Rapid development methodology: Rails targets zero turnaround time development, where developer changes to the system are made instantly available for use and testing. Rails development eliminates the build cycle, the deploy cycle, and the container restart cycle. Java, at its best, only approaches zero turnaround time development, attempting to compile and deploy in the background with certain IDEs in certain environments. Ultimately, changes to configuration files such as those used by Struts end up causing container restarts which sap developer time. Single-stack application framework: Ruby on Rails includes all the components necessary to build complete web applications from the database forward (even including a pure-Ruby web server for those who wish to develop immediately without setting up a web server such as Apache or lighttpd), providing object-database linkage, a Model-View-Controller framework, unit and functional testing tools, out-of-the-box support for AJAX and stylesheets, support for multiple templating systems, multi-environment deployments, support for automated local and remote deployments (via the included Switchtower utility), inbound and outbound email support, web services support, etc. With Java, choices abound, and we have seen that there is actually too much choice: core architectural components in wide usage are being actively deprecated by their developers and well-considered choices made at one point in time are revealed to be dead-ends 6-12 months later. The Java web community is in a state of high flux, and the apparent direction that the community at large is taking appears to not bode well for the type of architecture we had envisioned. AJAX UI support: We have placed a high priority on streamlining the user interface for the CenterNet rewrite. The most viable technology for streamlining user interaction is AJAX (“Asynchronous JavaScript and XML ”). Ruby on Rails’ support for AJAX is powerful, flexible, terse, and well-integrated into the framework. The development of the Prototype AJAX library (a popular AJAX and JavaScript effects library) has been driven by Rails development feedback. What progress we have been able to make with AJAX under our Java framework has been through using Rails’ AJAX output as a guide to implementing JSP taglibs in Java. AJAX development in Java has proven to be extremely difficult. IDE automation not required: It is well-documented that much productivity in Java comes from the widespread use of tools to automate code writing and change. The “shotgun code smell” which permeates the Java web stacks is reigned in with editors such as Eclipse which support XDoclet annotations. The verbosity of Java Bean getter and setter methods (code which must be written to read and write every value accessible in a Java Bean object) is mitigated by editors which support hotkeys or other automation methods for creating those methods. Other tools (e.g. Middlegen) use known data to write code or verbose XML configuration files. Java is widely defended as “productive”, so long as the proper tools are in use to mitigate the impact of voluminous code and configuration data. The learning curve, however, for the use of these tools is high. Indeed, even the process of choosing which tools to use comes with a high research burden and high risk of abandonment (see above, under “Single-stack application framework”). It is this author’s belief that much of the research into annotations and Aspect-oriented programming is geared toward making the widely adopted Java language productive for developers and maintainers. In contrast, Ruby and Rails are terse and dynamic, support AOP -style separation of concerns (Rails also has AOP tools available, though not widely used), minimize configuration, and consequently are productive outside the use of code/config generators and IDE environments designed to minimize necessary (to Java) redundant typing. We have found Ruby to be extremely flexible in this regard, allowing us to quickly alter functionality without touching framework code. Dependency Injection (IoC): Dependency Injection (or Inversion of Control) is a pattern widely used in Java development to help streamline the process of connecting and/or replacing components and services inside an application. Java’s type system, as well as the abundance of components available (and, some would argue, the high degree of volatility in the availability of usable components) makes Dependency Injection a necessary technique. Dependency Injection is also useful in testing under Java’s typing system, where replacing components with stubs or mock objects can be cumbersome. There exist small Ruby libraries for implementing Dependency Injection, but Ruby’s dynamic nature and open typing makes Dependency Injection much less of a necessity than in the Java world. Open-source: Both Ruby and Rails are freely downloadable and redistributable open source products. While parts of our Java stack are open source (under licenses of varying flexibility), much of Java is encumbered by restricted licenses, despite Sun’s PR push to cast Java tools as “open source” software. A true open source language and framework provides an ultimate escape from both vendor lock-in and unpatched software defects: simply read the code and change things yourself. Database agnosticism: Rails supports a wide array of databases, including all the common production database platforms. Our high-level goal of maintaining database platform independence is supported by Rails. Later adopters wishing to customize CenterNet for use on other databases would not be hampered by our choice of Rails as a platform. We have had great success in developing, testing, and deploying Rails on multiple databases simultaneously—we are currently using PostgreSQL and Oracle. Web services support: Rails supports the most popular web services protocols, including XML -RPC, SOAP , and WSDL . Rails’ flexibility with regard to external APIs enables addition of further web services interfaces easily. The popular Rails weblogging software Typo serves as an open example of the ease with which new APIs can be added to Rails applications. Integrated unit and functional testing support: Our iterative development process for CenterNet relies heavily on the ability to build comprehensive automated tests. Testing in Rails is simple, well-documented, terse, and integrated into the Rails framework. Rails builds the necessary scaffolding for unit and functional tests automatically, and tracks test/code ratios automatically. Rails includes native support for mock object testing and database fixtures, streamlining out-of-container testing. Since Rails doesn’t require a heavy object container, the headaches of Java-style “in-container” testing are alleviated—allowing for faster http-level and in-browser testing. In contrast to Java, with its numerous heavy testing frameworks, where Java requires the absorption of entire 300-page texts for each layer of the testing hierarchy, everything one needs to know about Rails testing, which covers the same scope, can be learned from a couple of short chapters. The availability of ready testing has greatly eased our development and caught a large number of early defects which would have been difficult to otherwise exorcise. Production vs. Testing vs. Development: Rails enforces the concept of deployment environments. Rails comes stock with three environments: production, where caching and performance enhancements are enabled, code is assumed not to be under development, and developer information is not passed back to the end user; testing, where a volatile database is made available for the purpose of running test suites; and development, where performance enhancements are disabled, a development database is in use, developer tools and feedback are in force, and the emphasis is on agile development methodologies expecting zero turnaround time development. Selection of an environment is enabled through one master switch. Rails allows for any number of additional environments to be created, and setting up an environment is trivial (a new file with a few lines of configuration information). In contrast, while Java allows for distinguishing environments, tool support is lacking, and any configuration switch must necessarily touch numerous components in an application. MVC separation of concerns: Rails integrates the Model-View-Controller architecture in a lightweight manner. Separation of business logic from database logic and from user interface is handled cleanly, decreasing coupling and providing the benefits of orthogonality. The Java MVC architecture of choice, Struts—which is being deprecated by its author in favor of non-MVC “component” tools which are the current rage in the Java world—places heavy overhead on the developer creating the controller and action building blocks of the front-end, while the struts-config.xml XML configuration file is verbose, unwwieldy, untyped, and requires container reloads on changes, thwarting attempts at “zero turnaround time” development. Share-nothing horizontal scalability: While Rails benefits from increasing the horsepower of servers on which it runs, Rails is designed from a “share-nothing” mindset. Each Rails thread is self-contained horizontally (i.e., it shares nothing with other Rails threads), and vertically (i.e., it shares nothing with the web server or the database server). When deploying Rails for scalability it is typical to use lightweight high-speed server tools such as lighttpd and FastCGI to allow for cheap and easy horizontal scalability. Add more cheap commodity PC hardware and the application handles more load. Add memcached (essentially, a distributed memory cache on the local network) to some of those cheap systems and even the database tier is scaled cheaply and horizontally. Java also offers horizontal scalability—the baseline deployment being higher (Java + application server tends to have a heavy footprint), the configuration burden is typically higher than that for scaling Rails, and it is often harder to set up a Java framework in a “share nothing” manner. Memcached support: Rails provides integrated support for memcached, written by the developers at LiveJournal to allow them to scale their Perl+MySQL application to handle traffic loads on their 2.5M+ user base. Utilities: The Rails breakpointer allows developers to set a breakpoint in web application code, which is nothing revolutionary. What is impressive is that once this breakpoint is hit by the web application, the developer has a console available at that line of code where he can inspect and modify any and all Ruby constructs (including classes, methods, etc.), and then can send the application running from that point (tellingly, the breakpoint library’s source code is shorter than a typical Java Hibernate XML mapping file for a single database table). Additionally, there are tools for running short automation scripts at the model level, and even an interactive console for working with Rails models and model constructs. Java’s verbosity, typing, and compiled nature makes development of a comparable tool not only complex (if not impossible), but would make its use cumbersome. API stability: Rails is nearing its “1.0” release, which is not only psychologically important but acts as a milestone for API stability—major changes to the framework would be avoided in the 1.x series. After the publication of the Rails guidebook Agile Web Development with Rails, the Rails development team (including, most visibly, David Heinemeier Hansson) are on record with a commitment that they will not “break the book” on the road to 1.0. Essentially, the 1.0 APIs are here, with whatever modifications are necessary being backward compatible with those documented in The Book. Following the development of Rails since its first public release, this author notes that there have been very few incompatibilities introduced even from the earliest stages. Language stability: Ruby as a language is stable, with the language developers historically working hard to maintain backwards compatibility. Java, the language, has undergone significant changes with each point-release. The newest standard, Java 1.5 (or “Java5”) introduces annotations, generics, and other constructs which are significant language changes reportedly geared towards addressing shortcomings in the Java language. It is also worth noting that Ruby is of comparable age to Java (Ruby is about 2 years older). Project adoption of Ruby: The CenterNet rewrite project is already using Ruby for build automation dependency maintenance, dependency graph construction, and planning documentation. The CenterNet build will compile the Ruby interpretor (when necessary) to examine project dependencies and generate build, documentation, and planning artifacts. Additionally, Ruby has been used to prototype outcome charting for the old CenterNet system’s new Residential functionality. We have introduced Ruby on Rails to the rewrite project for rapid UI prototyping purposes, and it appears likely that it will be used in this role for the forseeable future. High-level adoption: There has been a repeated pattern of defection from Java to Ruby on Rails among the high-profile luminaries in the Java community. Typically, a high-level Java figure will encounter Rails, speak out against the claims of improvement over Java, learn more about Rails, spend some time developing with Rails, and then retract past opposition, and begin using Rails for new project development. There are numerous such accounts on the web, here are a couple of the more interesting ones, from David Geary and another (deep in the heart of a flame-war about one of David Geary’s earlier posts), from Bruce Tate. This defection pattern continues.

Considerations

There are some important considerations to take into account regarding the adoption of Ruby on Rails at this stage in our project.