I spent a few hours getting to know Grails. This post is from the perspective of someone not so much learning Grails as evaluating Grails. It’s not so much “Let’s have dinner and plan our wedding” as “Let’s have coffee and see if we wanna do this again”.
This is written against Grails 2.0.3.
- The Documentation. Well organized, well-written, well-formatted, and lots of code.
- The Shell. Once Grails is on your path, you type “grails”, and you’re in the shell. Apparently Grails used to have this thing called “interactive mode”, and the shell replaces that. Anyway, once you’re in the shell, you run your app with run-app, and *most* of your changes get picked up without a container restart. Some of us call this “F5” development. This is what I want
- The Console. On the shell, you can pop up a “console”, which is a simple text editor where you type in commands, execute them, and see results. This made it super fast to play with GORM once I created a few domain classes. Bonus points to all the keyboard shortcuts, even in the console.
- The Plugins. Egads. I typed “list-plugins” from the shell, and the list was so long I had to change my console buffer and re-run the command. I was looking for a few specific solutions: Flex, Excel, Mongo. All there, sometimes more than once.
Time to Un-WTF
When evaluating anything new, it’s typically only minutes before you hit your first WTF? That’s expected, and the absence of early WTFs would concern me. The question becomes “How long does it take me to go from WTF to not WTF?”. And, “What’s the process of getting there?”. For me, if I’m in a debugger trying to figure things out, that’s the worst. If I’m Googling, bad. If I’m on StackOverflow, bad. I want the early WTFs answered either in the app/code itself (via helpful error messages perhaps) or on the “official” docs. Let’s look at a few:
A New User
In the shell, I typed "generate-all helloworld.User" to generate a domain class, views, and a controller for a "User" object. My goal was to see the list of users, create a user, and edit a user. This is my story
Invalid syntax at user
After creating the scaffold files, I restarted my app. Bam, first error. Fortunately, it was kind of obvious, because Grails spit out exactly the SQL it was trying to run but could not. It was trying to generate the tables in SQL Server for my new User class, with sql something like:
create table user (id....). I copied out the code, pasted into SS Mgmt Studio, and tried it myself. Same error. This is a good thing. No magic, easy to reproduce. And, for my simple case, the error is kinda obvious once it's in SSMS: "user" is a reserved word and I can't create a table with that name
Thus, for my first "time to un-WTF", I had to answer the question: "How do I specify a different table name for this class?". Impressive. First, I was able to find it in the official docs, without need to resort to Google. Second, because of how well the docs are organized, it was very simple to find. The docs don't have their own "search" feature, but I knew enough to go to the GORM section, and from there just pop up CTRL-F and search for "table name". I'm talking sub-minute time to Un-WTF here.
I added a row.... why won't it show up?
Next step was to create a new user. I opened the link to my newly generated controller, and it showed me an empty user list and a button to create a new one. Ok, let's press that. The form was empty, because I hadn't specified any properties on my domain class. Whatever... let's click that "create" button and see what it does. BAM. Error. I'll get to that later, because it was the most painful part of my first date. OK, so, creating a new record errors... Let's add one manuallly to the database, pop up the user list and see what happens
Nothing. No new rows appeared on screen, even though I could see them in the database. I mean, I know I don't have any properties on the class, but shouldn't the list of users at least have a "1" and a "2" indicating row number or something? Shouldn't there at least be some visual indicator that *something*, anything, is working, that it's actually pulling my data from the DB? So, my first guess is that something is caching results, and that since I added the data manually GORM doesn't know about it. So, off I traipse through the code to find anything that looks cache-like. I found 2, and I disabled them. Still, nothing. Grumble.
On a whim, I added a few properties to the class, and typed generate-views again. Hey, look, users! This obviously makes a lot of sense… if my newly generated domain class didn’t have any attributes, how would it be able to generate a meaningful scaffold for the “list” view? Of course it won’t show anything.
Here’s my (admittedly small) complaint: For anyone evaluating, they’re following the “Getting Started” guide, mostly. So they’re following along, typing “generate-all …”. Then they go to their app, hit refresh, and become wide-eyed with wonder and unicorns. ZOMG it’s all just there! Except, it’s not. Because even if you have data in your database, you see nothing. I have to imagine that others go through this. Bottom line: it’d be nice if there were a simple visual indicator (a row number) that clues me in to what the real problem is. Maybe I’m an odd case in that I’ve been working with ORM long enough so that when something unexpected happens, I blame it rather than the more obvious culprit of simply having no properties.
The Magical _form
I admit to some surprise at seeing both a “create” and an “edit” gsp for editing a user. Where I come from, we keep this as a single template and use a little bit of logic to determine whether the user is new or existing, and adapt accordingly. It minimizes duplication. So seeing two files gave me the whillies. 40 lines of nearly identical code… Shudder. Fortunately, I also saw this “_form.gsp” template in there, and upon inspecting the code I see this:
OK, so at least the actual form isn’t being duplicated… just the bits around it. This leads me to my next WTF: why is the file named _form.gsp, but the g:render asks for “form”.
Since I was in the IDE, my first thought was to look at the grails.tld file to find some answers. No dice. So, back to the docs. I look in the right hand column of the page, and I see Tags. Click that, it pops open, and I see “render”. Click that, and the page is mercifully short. A few scrolls down, and “_” is very obviously pointed out and described.
So, from my mullet-headed tiptoe through grails.tld to answering this question, sub 5 minutes. Not bad!
Hibernate Session Error
Anyone who’s worked with Hibernate enough comes to simply expect Hibernate session problems. Picture the movie Pulp Fiction, and the famous basement scene. Remember The Gimp? Yeah. That’s you. The Hibernate Session object is Zed, strapping the gimp ball around your face.
Anyhoo, here’s what I get upon my first attempt to save a new, *empty* domain object:
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
In my first hours working with Grails, I spent the most time on this problem. I’m not going to belabor it. I googled, I read all manner of docs, I swore. I couldn’t find anything. Finally, throwing out a hail Mary, in Config.groovy I changed grails.gorm.autoFlush from false to true. And then, just like that, it started working.
I have no idea why. I know I don’t like it. I know that sooner or later, I’ll need to figure this out.
What’s also troubling is that now, if I turn it back to false, I can’t replicate the error. So… was it because I had no properties? Was it something else I changed? And why didn’t google turn up anything obvious? Am I the only bonehead who follows (mostly) the docs, creates a new empty thing with generate-all, and then actually tries to run it? Probably.
I have zero doubt that I am the problem here. I’m used to it. I am unsettled at it nonetheless.
Code Duplication in the scaffold files
The duplicate files for create/edit puzzled me. That’s all… nothing more. I guess it sticks in my crawl because, as an evaluator, when working with codegen stuff, you base your decisions in part on the code that is generated. I, at least, expect the generated code in a Convention-over-Configuration framework to give me an idea of its conventions. It should attest to what it values. The generated code says “We, the makers of this framework, consider this to be a thing you’d want to do, too, otherwise we would not generate this code”.
Fat Controllers, No Services
Running generate-all, you get no Service objects. All the “work” gets done in the Controller. This is a very Bad Thing™, because the first thing that most of us are going to do is yank that crap out of the Controller, create a service class, and put it in there. Why this is not the default for the generated code mystifies me. I’d like to give these fine folks the benefit of the doubt, and perhaps all the smart kids know that “duh, you never use generate-all”.
A large part of learning a new thing is learning its community’s conventions. That takes time. A large part of evaluating a new thing is deciding whether what you see warrants that learning time investment. Just sayin’.
Conclusion and Next Steps
Even as I hit problems during my first date with Grails, I appreciated that it usually took me little time to solve them. This is huge for me when evaluating something… I expect problems. How quickly, and what steps I must take, to solve them are most important, and Grails performed admirably here.
That, coupled with the awesomeness mentioned above, most assuredly warrants a second date with Grails. In fact, several times during my few hours I felt like the guy in the big white tent, “Alleluia”-ing and “Testify!”-ing.
Next up I want to learn:
- How to expose a remote api, via json and amf. Is it something special, some annotation? Or is there “nothing to see here”? We shall see
- What steps are involved in hooking up a service layer? Given that there’s a ‘create-service’ command, and the project explorer in SpringSource Tool Suite shows a special marker for “services”, I presume this will be a simple as using and generating controllers. The only real question then will be figuring how how to inject dependencies, and I assume this is as simple as adding properties and letting Spring auto-wire them
Not sure if it's related but I had Hibernate issues with Mura ... the DB conn kept dropping out and I was left thinking WT? ...
Solved it by deselecting Maintain Connections in CFAdmin ...
Data & Services > Datasources > Maintain Connections OFF
Post a Comment