Code for this blog can be found on github at
https://github.com/galapagosfinch

Friday, May 27, 2011

Adding Services to Grails Controller Templates

One of the first things we learn in grails is that it can auto-generate domain classes, controllers, services, yadda yadda yadda.  What we *don't* usually learn is that the template for controllers is really minimal - in fact, it was even buggy during certain releases.  The Controller code that is generated does not make use of Service classes, so in your hurry to see something happen, you end up with business logic in the controller when it really should have been relegated somewhere else.

I'm assuming you already know how to create domain classes, controllers, and services. So, before you actually do that, first thing to do is to fix that in the template.  Install the templates via

grails install-templates

This will populate the direct src/templates/ with three subfolders.
artifacts
Contains the template files for "create-*" commands; relies on dynamic scaffolding
scaffolding
Contains the template files for "generate-*" commands; contains everything you need
war
Contains the web.xml file which will control the startup/configuration of your war file (we'll come back to this later)
So, to make your controllers start using services, modify the beginning of the scaffolding/Controller.groovy file to have a service injected:

<%=packageName ? "package ${packageName}\n\n" : ''%>class ${className}Controller {

    static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
// Add the service that matches the controller
    ${className}Service ${domainClass.propertyName}Service

    def index = {
        redirect(action: "list", params: params)
    }

and use it in the methods:

def save = {
    def ${propertyName} = new ${className}(params)
    // below, use service instead of direct instance call
    if (${domainClass.propertyName}Service.save(flush: true)) {
        flash.message = "\${message(code: 'default.created.message', args: [message(code: '${domainClass.propertyName}.label', default: '${className}'), ${propertyName}.id])}"
        redirect(action: "show", id: ${propertyName}.id)
    }
    else {
        render(view: "create", model: [${propertyName}: ${propertyName}])
    }
}

So what's all the fuss?  All we did was replace a direct call to "save" with a delegation to a service which will most likely just call "save".  Having a service gives you (a) a transaction around your work, (b) a clear separation of concerns, where Service does the work and Controller manages connecting Model to View, and (c) reusable code in case you need to use it elsewhere, like a web service or Spring remoting.

Remember, this is the template.  I'm not saying that every Controller needs a matching Service, I'm just saying that starting from that point will probably get you better separation of code in the end.  Cleaner code is more maintainable.  speaking as someone who lead a project where all business logic was embedded in the controllers for the first five months, well, let's just say that I wish someone had sent me a link that looked like this when we were just starting...

The Controller methods can get much more complex; we'll talk about REST later and see how to put your data out there in JSON, XML, and "whatnot".

No comments:

Post a Comment