Dec 142009
 

It is well advised to place application and business logic in service classes when doing any programming and indeed the Grails user guide does advise this. However the default templates do not do this so there is no example or dare I say it best practice example that I could find on the web. So exactly how do we get the controller and service to interact and retain all the functionality provided by the templates and generate-all?

These are the methods I have come up with, hopefully these and others can be developed and maybe even be considered for inclusion in the Grails templates at some point in the future. For a new Grails user there might be a little more to learn at first but for any real world application using service classes with the controllers is an absolute must and it’s much harder to have to figure it out than if the basics were already in place.

List

The default is nice and simple:

def list = {
    params.max = Math.min( params.max ? params.max.toInteger() : 10,  100)
    [ bookInstanceList: Book.list( params ), bookInstanceTotal: Book.count() ]
}

This will ensure that you don’t load entire large database tables into memory at once while allowing pagination through the entire table. But you will very quickly want to start adding logic to this. Any more than a few pages of results and we’ll need to filter the results by deleted/trash, current user, date, status etc. Sure adding Book.FindByTrash(false, params) is a good start but this quickly gets messy and now we are into application/business logic and that does not belong in what should be a thin controller layer.

So what we need is a controller that looks something like this:

class BookController {
    def bookService
 
    def list = {
            def result = bookService.list(params)
            if(!result.error) {
                return [ bookInstanceList: result.bookInstanceList,
                        bookInstanceTotal: result.bookInstanceTotal ]
            }
 
            flash.message = g.message(code: result.error.code, args: result.error.args)
            redirect( url: resource(dir:'') )
    }
 
}

No application/business logic what so ever, the controller is left free to just control. I have seen recommendations that the controller should do some sanitising/validation of incoming params but this does not work for me for two reasons. 1) Validation/sanitising can get complex and is often tied to business logic. 2) The service should validate/sanitise is own incoming data since it may be called from many places in the application, so why do it in two places?

Now the service class:

class BookService {
    boolean transactional = false
 
    def list(params) {
        def result = [:]
        def fail = { Map m ->
            result.error = [ code: m.code, args: ["Book"] ]
            return result
        }
 
        params.max = Math.min( params.max ? params.max.toInteger() : 10,  100)
        result.bookInstanceList = Book.list(params)
        result.bookInstanceTotal = Book.count()
 
        if(!result.bookInstanceList || !result.bookInstanceTotal)
            return fail(code:"default.list.failure")
 
        // Success.
        return result
    }
}

Straight away we can start adding logic to the correct place. Have another reason this operation could fail? Just add it, create a custom error code and you’re away. If we ever need to list in another view that logic is all captured in the service class. We set transactional = false because by default every service method is encapsulated in a transaction and we simply don’t need that for every method.

Update

List is a nice simple example but it’s the update action that immediately shows the most benefit.

The default:

def updateDefault = {
    def bookInstance = Book.get( params.id )
    if(bookInstance) {
        if(params.version) {
            def version = params.version.toLong()
            if(bookInstance.version > version) {
 
                bookInstance.errors.rejectValue("version", "book.optimistic.locking.failure", "Another user has updated this Book while you were editing.")
                render(view:'edit',model:[bookInstance:bookInstance])
                return
            }
        }
        bookInstance.properties = params
        if(!bookInstance.hasErrors() && bookInstance.save()) {
            flash.message = "Book ${params.id} updated"
            redirect(action:show,id:bookInstance.id)
        }
        else {
            render(view:'edit',model:[bookInstance:bookInstance])
        }
    }
    else {
        flash.message = "Book not found with id ${params.id}"
        redirect(action:list)
    }
}

The adjusted controller action:

    def update = {
        def result = bookService.update(params)
 
        if(!result.error) {
            flash.message = g.message(code: "default.update.success", args: ["Book", params.id])
            redirect(action:show, id: params.id)
            return
        }
 
        if(result.error.code == "default.not.found") {
            flash.message = g.message(code: result.error.code, args: result.error.args)
            redirect(action:list)
            return
        }
 
        render(view:'edit', model:[bookInstance: result.bookInstance.attach()])
    }

Now there’s an improvement in controller action. If all went well we simply redirect to where ever we want. If something failed then we can inspect the error code and determine where to go next, in this case back to the list action or render the edit view to show the user the validation errors which are bundled in the bookInstance. I found that it was necessary to call attach here since some times after coming out of a transaction the instance gets detached.

The service method:

    def update(params) {
        Book.withTransaction { status ->
            def result = [:]
 
            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.bookInstance && m.field) 
                    result.bookInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Book", params.id] ]
                return result
            }
 
            result.bookInstance = Book.get(params.id)
 
            if(!result.bookInstance)
                return fail(code:"default.not.found")
 
            // Optimistic locking check.
            if(params.version) {
                if(result.bookInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }
 
            result.bookInstance.properties = params
 
            if(result.bookInstance.hasErrors() || !result.bookInstance.save())
                return fail(code:"default.update.failure")
 
            // Success.
            return result
 
        } //end withTransaction
    }  // end update()

Although not strictly required in the above code, programmatic transactions are used as this is the action most likely to require it and therefore a good place for the example. I have also found programmatic transactions more useful than the default since we often want to cause a rollback for reasons outside of the bookInstance.

The Rest
The remaining actions and full classes can be found here:
BookController.groovy
BookService.groovy

messages.properties

default.list.failure=Could not generate list for class {0}.
default.not.found={0} {1} not found, it may have been deleted.
default.delete.success={0} {1} deleted.
default.delete.failure={0} {1} could not be deleted.
default.update.success={0} {1} updated.
default.update.failure={0} {1} could not be updated.
default.create.success={0} {1} created.
default.create.failure={0} could not be created.
default.optimistic.locking.failure=Another user has updated this item while you were editing, please check the updated values.

It would be nice to have the failures come up in red.
I find myself placing g:hasErrors and g:if test=”${flash.message}” divs in most views so they may as well be added to all views.

Conclusion

Perhaps this setup should never be the default/only option. All the extra service classes would create some overhead.
Maybe a plugin like the http://grails.org/I18n+Templates+Plugin.
Or (my vote) a couple new scripts with templates:
grails generate-serviced-controller
grails generate-service

If you’ve read this far then you probably have some comments :-)

Gavin Kromhout:


Thank you for visiting.
Do look around.
Do leave a comment.

  15 Responses to “Grails Service And Controller Interface”

  1. Finally! Long time I was searching for an example like this. Everyone talks about keeping controller clean, but nothing found how to do this. Thanks for writing!

  2. really good article. thanks!

  3. Thanks guys, glad it made logical sense to someone else.

    Sebastian, I always wish I could read more German as your site looks great, the little Afrikaans I have just doesn’t cut it :-)

    Junmin, I hope it helps and I’m sure you guys will improve on the interface concept.

  4. Grails Service And Controller Interface…

    It is well advised to place application and business logic in service classes when doing any programming and indeed the Grails user guide does advise this. However the default templates do not do this so there is no example or dare I say it best practi…

  5. Hey thanks! Your post helped figure out how to pass args to the message tag from the controller!
    Good work!

  6. Wow, very similar to what I do. One “moral dilemma” I ran into was that if you pass the request params directly to the service, you’re tying the service to the http request parameters. For example, if you’re updating a user, then you have to pass in a “user.id” or “userId” HTTP param, which then requires a User.get() to convert into an actual user object and validating. I agree that you do not want to put this kind of logic into the controller, but I also think it’s bad to put it in the service. Instead I use command objects for this kind of thing. I view services as living at the “domain” level, completely severed from the http request:

    other service -> domain-level inputs -> my service
    HTTP params -> command object -> domain-level inputs -> my service

    For example the service is taking a User object instead of a userId. You can pass the command object directly to the service and not have to modify the signature.

    The only problem is that as of right now (Grails 1.3.7), command objects are not first-class Grails artifacts, so they can’t be dynamically reloaded/enhanced via plugins/etc.

  7. Yes command objects seem nice but I haven’t really used them for the same reasons that I don’t put too much logic in the domain class, reloading and practicality.

    I believe this will be significantly improved in Grails-1.4 with reloading of domain classes.

    I use a convention of DomainService.groovy for service classes that are “domain level” and other services are actual services.

    I don’t find myself having the same “moral dilemma” with what to pass in to the service, I view it as map-in, map-out (embrace the dynamic language). So the map-in can be a “user” object or “map” object since with groovy “User.get(params.id)” will work for “user.id”. The domain level services provide the db interface layer, not purist OO but neither is skinny domain classes with helper services. I try to avoid “userId” but it happens in a few cases.

  8. Thanks a lot for sharing this tutorial, it’s a great starting point for me

  9. Nice.
    Already think of it, found this searching more resources on google on scaffolding controllers + services instead of just controllers in order to further expand functionality with spring-security.

    Did you made any steps by now on extending grails templates and scaffolding to include services?

  10. No I haven’t but there was just recently a post on the Grails Dev mailing list about just that.

  11. This is a great tutorial, I’m glad I found it early in my project. I knew there had to be a better way to handle errors with services.

    • Thanks, glad it helps. I still don’t feel like it’s finished, there was some talk on the Grails dev mailing list about this again recently. One other thing I can recommend for you is make sure you have set up everything with UTF-8. Grails is out of the box but the database connection and backend may not be.

  12. Yes indeed! 2014 and Grails 2.3.8 and there’s still a dearth of good examples for how to really use services in Grails. I intend to apply this model to my application forthwith! Thanks again!

    • Glad this pattern is still proving useful :-)

      • Actually, I just realized today that this is exactly what command objects are for. This pattern still has some use in cross-domain service methods, though :)

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>