I started writing this post a month ago but delayed posting it until now. We are almost all the way through a re-write/re-design of vogogo.com in Django and I must say it has been a pleasure.
The comments below represent my findings after developing and deploying several Grails production applications over the last several years.
Complicated Controllers
Most of the examples you see built in Grails operate on a relatively simple domain model. i.e. Author has Books, a Book belongs to an Author. Creating a BookController with CRUD closures or possibly moving logic into services seems like a good idea and in this case I would agree. Unfortunately most systems are more complicated than that. Initially we had things laid out in a manner that made sense but as time went on, 2 years, the controllers became more and more complicated dealing with problems such as transient errors or having to do a Hibernate.initialize() before re-displaying a form to the user because the Hibernate session was destroyed on error.
Domain Class Constraints
Part of the controller madness was the decision to put constraints in the domain class. This leaves you with a false sense of leaving error checking up to the domain. As we discovered this isn’t the best option. As a result we had lots of controllers with logic translating the domain from how it is displayed to how it is stored and back again. A better approach would be to use a command object for each page and not use domain class constraints at all or as a last minute sanity check before persisting.
Object Oriented
Grails can’t take all the blame here though. I’ve come to the realization that building an object oriented web application is so difficult to get correct and nearly impossible to modify without quickly becoming a hair-ball it shouldn’t be attempted. At the very least classes can be employed as data holders only, maybe in rare cases when a polymorphic relationship could be of benefit. VTX did make use of using Hibernate as a factory. For example:
def transactions = Transaction.findAllByStatusAndDate('PENDING', new Date()) transactions.each { it.clear() }
Where Transaction was extended by different types with a clear() implementation that was specific to that class.
I’ve thought about creating a function based web framework in Groovy akin to Django but decided against it for performance reasons.
Dyanamic Finders
At first Transaction.findAllByStatusAndDate(‘PENDING’, new Date()) looks like a good idea but I get the feeling that it adds substantial complexity and startup overhead to dynamically add the finder methods to your domain classes at runtime and I’m not sure it is worth it. If you are building a feature and add a call to a dynamic finder you have to add mockDomain(MyClass) to every test that directly (or indirectly) makes a call to that finder.
In reality I think Transaction.findAllWhere(status: ‘PENDING’, date: new Date()) that returns a list or findWhere that returns one object is a much better solution. One that could be handled without having to slow things down by dynamically adding things at runtime then working around the increased testing time with things like mockDomain.
Hibernate
I used to enjoy hibernate back when I was a Java frog boiling in water. I got used to reading stack traces, writing complex HQL and mapping .hbm.xml files. Grails hides most of the nasty stuff away from you but it is still there waiting to burn you with a transient, flush or session error. I can appreciate what the Grails and Hibernate guys have done but I just can’t stomach some of the assumptions that have been made anymore. It might seem easier to let the framework persist data changes to the database implicitly at first, but forcing the developer to explicitly manage it isn’t that bad and it avoids most of these issues.
Slow Test Time
We now have 4 systems built entirely in Grails and are approaching 300,000 lines of Groovy code including tests. If you were to run all tests in all four projects one after another you would be looking at close to 40 minutes on high-end hardware. VTX has 2700 unit and 1041 integration tests and it takes approximately 15 minutes for grails test-app to complete. In the past it has taken up to 25 minutes due to since removed features and older hardware. The trade off appears to be, write complicated unit-tests with large test setup and lots of mocking or write simpler to the point integration tests that are slow, even when run individually. We moved to an in memory database and loaded the project up into a RAM disk for a 30 second performance increase out of 15 minutes. Even with the highest end processor you can buy right now I’m guessing it would only shave a minute or so off above what we already have.
We did try groovy++ and it did look promising but after hitting a few roadblocks early on around closures manipulating variables in their parent scope that required re-writing and testing we decided to wait and see if Grails 2.0 helped.
Groovy
Groovy is one of the nicest curly-bracket languages to write and maintain in my experience. Being able to drop a lot of the punctuation noise, the ability to create domain specific languages and generally trying to stay out of your way results in clean easy to read code. Throw in closures and the ability to dynamically modify objects at runtime via MetaClass makes it a real joy to develop in. Unfortunately, from what I’ve read, it suffers from some performance problems rooted in the JVM that affect all dynamic languages running. Apparently JSR 292 and the introduction of invokeDynamic in JDK7 is supposed to help/solve this problem but I’m unsure of an ETA of when it will be available in Groovy, when Grails will upgrade to the associated version of Groovy or what exactly the performance improvements will be.
Interesting to read your take. I definitely agree with your point on hibernate. Way too much complexity leaks through the veneer of simplicity grails presents. And slow tests definitely suck. I can’t say the bare Spring MVC project I’m working on now is any better, so it might be a problem common to the whole Java/Spring platform.Interesting point on constraints. Not sure I follow the OO argument though.I think groovy++ has been abandoned in favor of @CompileStatic and GEP10. Doubt it’ll be in grails before 2.1 though.So has has django improved on these points?
It will be interesting to see if we can upgrade one of the projects from 1.3.7 to Grails 2.1. We can’t upgrade to Grails 2.0 at the moment because of this bug: http://jira.grails.org/browse/GRAILS-8596 My OO argument wasn’t laid out very well so I will try again. After playing around with functional languages and putting serious effort behind moving to Django it has become apparent to me that trying to engineer an OO domain from a series of use cases isn’t the best approach. In Django we have implemented most business logic in modules containing mostly functions without classes, leaving only classes that Django requires (models etc..). New functionality is built without any concern for a domain, only the steps required to fulfil the use case and make it work. If at some point in this process you realize you screwed something up (we are only human), you just move/fix/modify your existing functions around without any concern for how this affects the domain (because there isn’t one). This seems trivial but in my experience big OO domains lead to tightly coupled patterns, so trying to move around code inside an OO domain results in you getting pulled into other parts of the system you aren’t concerned with because another part of the system is expecting a class that looks and behaves like X.Another improvement was to change how we store data. I have been playing with a few NoSql databases recently and I really liked the approach of storing data in a format that makes it easier to use even though there might be replication, opposed to storing it in a normalized fashion. This plus implementing logic as functions restricts Django’s model classes to just saving and retrieving data and the form classes as a way to present and capture data form the UI. Form classes and model classes in Grails would be roughly equivalent to command and domain classes.Django is flexible in that if you wanted to place logic into models (domain classes) you could but their constraint system is really just for database schema generation.If Groovy/Grails was faster we might have been able to accomplish some of these improvements with it. We are within a few weeks of completing development of the new site and it is radically different than the current one so comparing LOC etc.. is (even more) pointless. That said running all tests now takes 9 seconds.
I am not a fan of hibernate at all. To me, when I spend so much time tracing into a library, it is obvious that it is communicating errors poorly and using “assumptive reasoning”. Add to that trying to save tree structures that have both added and pruned nodes and you want to gouge out your own eyes. Give me a straight DB connection or a toolkit like JOOQ and I am quite happy to do all the “boilerplate” code that hibernate is trying to save me from because I know what is going on.
As far as Groovy is concerned, it is nice that it is powerful but I find the documentation almost useless. The usage examples are trivial and incomplete with plenty of assumption that you know their conventions. I wish the prophets of “convention over configuration” would realize that convention is not the same as lazy communication. Conventions should be documented.
Lastly, with Groovy the error reporting is extremely weak. All sorts of invalid code can be written and parameters set that will simply not do what is expected rather than generated even a warning.