GORM: Controller vs Service
Scaffolds, generated codes, and lots of tutorials in the web or in print usually show Grails controllers directly invoke GORM methods. You will often see a list(), a findBySomething() in there. Simple invocations, I really have no problems. Usually, I would think of controllers as really thin callers to the business layer of your app. A single call to get() in your “show” action is a call to a business method, quite simply. But chaining a lot of GORM inside your action:
def someAction = {
def book = new Book(params["book"])
def chapter = new Chapter(params["chapter"])
def snippet = Snippet.findByKeyword(params.keyword)
if(snippet) {
chapter.addToSnippets(snippet)
}
book.addToChapters(chapter)
if(book.save())
{
do something
}
else
{
do something else
}
def bookShelf = new BookShelf(params["bookShelf"])
bookShelf.addToBooks = book
book.bookShelf = bookShelf
if(!bookShelf.save()) {
etc.. etc..
}
....
}
A little messy. First, that doesn’t make our controller appear like the thin business logic caller that it should be. Another is that some parts of this example need to be transactional. For this case, we’ve got three different domain models (book, chapter, bookshelf) to persist in one go. That would probably rule out the withTransaction GORM method as well. So perfect situation to make use of a Grails Service. You can wrap around all these logic inside a service method, and make sure to make the service transactional. So in your controller action, you can simply have:
def yourService
def someAction = {
def book = new Book(params["book"])
def chapter = new Chapter(params["chapter"])
def bookShelf = new BookShelf(params["bookShelf"])
yourService.bookServiceMethod(book, chapter, bookShelf, params.keyword)
}
Now YourService should have something like:
boolean transactional = true
def bookServiceMethod(book, chapter, bookShelf, snippetKeyword) {
..same as codes above..
}
Take note that the transaction breaks (and hence triggers rollback) when a RuntimeException is incurred during the service method call. So be smart about placing those within the method logic.