Saturday, November 01, 2008

Converting legacy Rails apps to Grails : The Domain

A little story about legacies...
(skip this if you're not interested in hearing a sobbing sentimental story and just want to get to the good stuff)

First a short story about my usage of "legacy" in the title of the post. So, here it is : a few years ago, Rails explodes on the scene. Everybody around you who knows a thing or two about web apps start thinking and asking whether your next app should be in Rails instead of XXX [substitute your framework here]. Nevermind that you'll be writing an "enterprise application" that would most likely need to integrate with the rest of your infrastructure (Java, PHP, whatever), or that the said application might have some performance requirements (e.g. it actually needs to DO something, instead of just pushing a few form feelds from and to the database).

Alright, so, Ruby is cool, Ruby is all the rage. You bring in that intern that seems to be a Rails wizard, he totally blinded you with how he put together an app w/ 3-4 forms in less than an hour. Nobody on your existing team can do that : they want to "think about the problem", "understand what needs to be done", put some thought into how to do it, and only then start writing the code. Not so w/ your superstar intern : he's banging out page after page, form after form, it's glitzy and it's Ajaxy, your heart is about to melt from the love towards your unexpected intern saviour.



Fast forward to a few years later. Your intern is gone, he's onto his next new and exciting gig. Your loyal developers have learned a few tricks from the now "old and crusty" Rails app, you got the next version of your Java web framework and your devs are doing quite better with giving you the "quick forms" when you need them. Your customers, initially raving about how quickly they got their app, are now increasingly annoyed that when they ask for what is seemingly simple feature (e.g. hook into this other database that's not mysql, talk to that 'other app' that's been there for a while) and your estimates are way too high ('cause you have to write all that stuff from scratch). To make things worse, your developers actually popped the hood on the Rails app, and it's a big happy bowl of spaghetti : the controllers have their hands in everything : poking around the database, spitting back dynamic javascript groping the glitzy UI in the most unbelievable places (and btw, your devs don't want to touch it with a ten foot pole). When your company scored that big customer, everybody was enamoured by the cha-ching of the cash register, but nobody thought that all those new users will want to use your intern's app (which btw, turned out to not know much about web apps in general, as Rails was the first thing he learned), and now both new and existing customers are not so happy that it takes longer and longer for the app to service them. On top of that, there are very few people who do understand all the magic that happens under the hood in the Rails app, and there are yet fewer people who know how to scale it to the level you need.



That's the place where the phrase "Legacy Rails" really starts making sense. Sure, there are new releases that promise a little more glitz to your app, the framework is still being actively developed, and nearly everyone out there has heard of Rails by now. But now that you're in this situation, can you really put your job on the line that just this next release will have the promised silver bullet ? Or would it maybe be better to just move the game back into your home court, where you set the rules, your dev team knows the ins and outs of the technology like the back of their hand, it scales well, integrates with EVERYTHING you could imagine ? That's when you really want that little Rails locomotive to let off some steam and disappear into the distance just as quickly as it arrived.


Anyway, I digress :-) Back to what I was talking about : how do you migrate the app to Grails.


Now The Goodies

Below is a sample Rails model class that we'll use to talk about the migration:


class Activity < ActiveRecord::Base
has_many :activity_items
has_many :user_notes
belongs_to :competency_group
belongs_to :course

has_many :activity_item_assets

belongs_to :created_by, :class_name => "User", :foreign_key => "created_by"
belongs_to :updated_by, :class_name => "User", :foreign_key => "updated_by"

validates_presence_of :title, :instruction_text, :competency_group_id
validates_length_of :title, :maximum => AdminType::COURSE_TITLE_LENGTH
validates_uniqueness_of :title, :scope => [:competency_group_id]

end


And the equivalent Grails domain object:



class Activity {

String title, instructionText
Date createdAt, updatedAt


static hasMany = [activityItems : ActivityItem, userNotes : UserNote, activityItemAssets:ActivityItemAsset]
static belongsTo = [competencyGroup:CompetencyGroup, course : Course, createdBy : User, updatedBy : User]

static constraints = {
title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])
instructionText(nullable:false, blank:false)
createdAt(nullable:true)
updatedAt(nullable:true)
}

static mapping = {
table 'activities'
createdBy column:'created_by'
updatedBy column:'updated_by'
version false
}

}



1. Location

Both Rails and Grails have a specific place where you can keep your domain objects. In Rails, you keep it in the app/models directory (the Models directory in your NetBeans project), whereas in Grails it's in grails-app/domain directory (the Domain Classes folder in the NetBeans project).

2. Purpose

In both cases, the purpose of the domain objects represent the most important concepts in your application. Additionally, they typically are "persistence capable" (e.g. you can persist an instance w/ a single call), and they provide for a fairly simple specification of relationships w/ other domain objects, as well as allow for specifying validation rules.




NetBeans provides fairly basic support for creating the domain objects : you get a little wizard that asks for the name of the domain object and it creates the Groovy class for you. One of the cool things about how NetBeans handles Grails is that it doesn't create any new metadata (e.g. there's no project directory created), and because the NetBeans project system is based on Ant, the NetBeans project simply delegates the creation of the domain class to the Grails Ant scripts.

Note that when you're looking at the differences between the Grails and the Rails classes, you will notice that (by design), the Grails class is much more focused on the domain, whereas the Rails class is much closer to the database. Thus, for example, you will notice that in the last line of the Rails validation, it references the "competency_group_id". I would imagine this is where my lack of knowledge of the Rails CoC (convention over configuration) bit me in the back, but in a number of places (that I'll mention), the Rails code is allowed to reference "assumed" column names (based on the CoC), which is generally confusing, and also seems to be very refactoring unfriendly (e.g. what if at some point in life, I needed to have a slightly different colum name - would I have to hunt down all references to that column)???

3. Class properties & Relationships


class Activity {

String title, instructionText
Date createdAt, updatedAt


static hasMany = [activityItems : ActivityItem, userNotes : UserNote, activityItemAssets:ActivityItemAsset]
static belongsTo = [competencyGroup:CompetencyGroup, course : Course, createdBy : User, updatedBy : User]

}


Migrating the class properties & relationships is pretty straightforward. For each "simple" property in the Rails class, you can declare a corresponding one in the Groovy class. Declaring the equivalent relationships in the Grails class is also quite straightforward, as the relationship names are pretty much the same. Although there is nothing in Groovy that prevents you from using the Rails naming conventions for properties (e.g. user_name), the Groovy convention is very much like in Java - CamelCase.

This is probably due to my Java background, but I found the ability to declare a class member variable anywhere in the Ruby class much less readable than the equivalent Groovy class. What I mean is that, in the example above, the ":title" property is not mentioned anywhere before the validation constraint. Thus, in order to figure out the properties, you need to examine not only the relationships but also all validation constraints. Although in the Groovy class, the declaration of the relationships similarly defines properties in the class, but at least in validation, mapping, etc. you definitely need to refer to properties that are declared somewhere. I guess this probably comes down to preference, but in my opinion, looking at the Grails class, I can see all the available properties at a glance.

In a very similar manner, I find that even the relationship declarations are very much more readable in Grails. One glance, and I can recognize all relationship types (e.g. one to one, many-to-one, whatever) and the properties corresponding to those relationships. In contrast, although the same can be accomplished in the Rails declarations (e.g. if you specify all has_one mentions one after another), not all Rails model classes that I had to look at followed such a convention.

4. Validation

static constraints = {
title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])
instructionText(nullable:false, blank:false)
createdAt(nullable:true)
updatedAt(nullable:true)
}

Once again, migrating the Rails validations is pretty straightforward, although not all Rails validations had a 1:1 translation in Grails. This is where the validation section of the Grails manual came in very handy, expecially during the first steps in the conversion when I wasn't really sure how to convert from one constraint in Rails to its Grails counterpart.

One thing to notice that is subtle but different between the Grails and Rails validations. In Grails, if a class property is not explicitly declared as "nullable", it is by default required. On quite a few occasions during the conversion, after initially migrating the explicit Rails constraints, I found myself going back to the domain class in order to make some of the Grails domain class fields optional.

One final point on validation is the title uniqueness constraint. In Rails it looks like :

validates_uniqueness_of :title, :scope => [:competency_group_id]


And in Grails it is :

title(nullable:false,blank:false,size:1..AdminType.COURSE_TITLE_LENGTH,unique:['competencyGroup'])


The thing to notice here is that Rails directly goes to the colum name, whereas Grails just uses the property name declared in the class.

Once again, I find the Grails validation section much more readable, as all constraints are organized in one section, and they're organized around the concepts that a user cares about. Thus, when I'm thinking about the validations that apply to a title, I can specify all constraints in the title constraints, compared to the Rails style, where the declarations are focused around the constraints (e.g. when you are thinking about a concept in the domain, do you think "Hm, let me figure out which properties of this class might need a format constraint?", or do you think "Hm, let me see, a title, what kind of constraints might it need, maybe a format constraint?"

5. Mapping into the database



static mapping = {
table 'activities'
createdBy column:'created_by'
updatedBy column:'updated_by'
version false
}




As you see in the "mapping" section of the Grails class, there were a few attributes that needed to mapped explicitly. The reason I had to do this is that at least during the initial migration, it was preferable that the Rails and Grails app work off the same database schema, so that the two apps can be tested side-by-side on the same data.

The mapping of the Grails app into the Rails database was pretty straightforward, as they follow very similar naming conventions for naming the database columns. First, Rails has the habit of converting the domain class names to plural for the table names, thus I had to add the mapping in the Grails app to point to the same tables. Additionally, the Rails class explicitly stated that the foreign keys that connect the user and activity are "created_by" and "updated_by", whereas the default Grails naming convention for the foreign keys would have been "created_by_id" and "updated_by_id", hence the additional mapping.

You will additionally notice the explicit disabling of the "version" column in the Grails class. The issue here is that by default, Grails uses a "version" column in order to allow Hibernate to do optimistic locking in transactions. Note that removing the version column from the Grails app has its penalties; however, at least in the initial implementation it was more important to have the same database schema, than to focus on performance. Although initially (before disabling the column) Grails very gracefully handled the addition of the new "version" column to the database, it became an issue when the column was added on a populated database, and the version would receive a null default value. Thus, had I decided to keep it for each table, I would have had to update the version column manually to contain 0 (so that Grails could increment as necessary, otherwise a NPE came up when Grails pulled a null from the database).

It is recommended that such a version column is restored after the initial migration period in order to allow Hibernate to make use of its optimistic locking performance optimizations.

6. Non persistent attributes
By default, all attributes defined in the class are persisted in the database. Now, in the example of the User class below, the cleartext password really shouldn't be persisted. Rails gets around it by declaring it as a virtual attribute:

class User < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password

end



The Grails domain class takes a slightly different approach, where the field is still declared, but is also mentioned in a special class attribute to indicate to Grails that the field shouldn't be persisted , and the 'password false' in the mapping section to indicate that a column shouldn't be created in the database.


class User {
static mapping = {
table 'users'
password false
}

static transients = [ "password"]

}


7. Persistence events

class Foo {
def beforeInsert = {
makeActivationCode()
}
}

As mentioned above, Rails supports the ability to automatically hook into the persistence events , in the case above, to execute the specified closure at a particular point in the persistence lifecycle. Grails doesn't support this out of the box, but it is extremely easy to accomplish the same functionality by installing the Grails Hibernate Events Plugin, and then specify a couple of specially named closures

All in all, throughout the whole conversion I had the feeling that when the Grails folks sat down to figure out how to do things, they put a little bit of thought into how developers actually work with domain classes, what's readable, and what's not, whereas the Rails approach has a little bit more of a "hacked up" feel to it.