grails

Implementing Burt Beckwith’s GORM Performance – No Collections

Posted on February 7, 2011. Filed under: grails |

No More GORM/Hibernate Collections?

Mr. Burt Beckwith http://burtbeckwith.com/ gave a talk on improving GORM performance in Grails. His presentation is available at http://www.infoq.com/presentations/GORM-Performance.

A point of his was to not use collections on your domain objects because the collections may need to be fully loaded from the database before objects can be added.

I decided to use it on one of my work projects. This project has 18 domain classes. I ran into a few problems, scratched my head for a few hours, then I was able to fix them. It took about 1/2 day to update the project from a “hasMany/belongsTo” style to a no collection style. Here is a very-simplified domain that uses a typical gorm one-to-many cascading relationship.

Sample Shopping Cart Application

Lets start with sample code of a Shopping Cart. We have a Cart object that contains many Item objects.

Typical GORM Relationship

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {

	String name

	static hasMany = [ items : Item ]

	static mapping = {
		sort "name"
		items sort:"name"
	}

    static constraints = {
    	name nullable:false, blank:false, unique:true
    }

    String toString() {
    	"Cart $name"
    }
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {

	String name
	Integer quantity

	static belongsTo = [ cart : Cart ]

	static mapping = {
		sort "name"
	}

    static constraints = {
    	name nullable:false, blank:false, unique:['cart']
    	quantity nullable:false
    }

    String toString() {
    	"$quantity $name"
    }
}

grails-app\conf\BootStrap.groovy

import shopping.*

class BootStrap {

    def init = { servletContext ->
    
    	def cart = new Cart(name:"alpha").save()
    	cart.addToItems new Item(name:"apples", quantity:10)
    	cart.addToItems new Item(name:"milk", quantity:2)
    	cart.addToItems new Item(name:"bread", quantity:3)
    	cart.save()
    }

    def destroy = {
    }
}

Removing hasMany and belongsTo

Now, following Burt’s advice, we remove the belongsTo and hasMany relationship, and embed a the parent object in the child.

Now our code looks like this:

BelongsTo and HasMany Removed

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {
	
	String name
	
	static mapping = {
		sort "name"
	}
	
    static constraints = {
    	name nullable:false, blank:false, unique:true
    }
    
    String toString() {
    	"Cart $name"
    }
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {
	
	String name
	Integer quantity
	Cart cart
	
	static mapping = {
		sort "name"
	}
	
    static constraints = {
    	name nullable:false, blank:false, unique:['cart']
    	quantity nullable:false
    }
    
    String toString() {
    	"$quantity $name"
    }
}

grails-app\conf\BootStrap.groovy

import shopping.*

class BootStrap {

    def init = { servletContext ->
    	def cart = new Cart(name:"alpha").save()
    	new Item(cart:cart, name:"apples", quantity:10).save()
    	new Item(cart:cart, name:"milk", quantity:2).save()
    	new Item(cart:cart, name:"bread", quantity:3).save()
    }
    
    def destroy = {
    }
}

Here’s what changed.

in Cart.groovy

  • removed static hasMany = [ items : Item ]
  • removed items sort:”name” from mappings

in Item.groovy

  • removed belongsTo
  • added Cart cart

in BootStrap.groovy

  • Changed the way we created child objects.
    • from: cart.addToItems new Item(name:”apples”, quantity:10)
    • to : new Item(cart:cart, name:”apples”, quantity:10)

New Problems and Solutions

Are we done? Of course not – it’s not quite that easy. There were a few other problems that needed to be addressed:

  1. No longer have easy access to the collection of child items.
  2. Sorting of collections (defined in mapping) no longer takes place.
  3. Can’t delete the parent object because of foreign-key relationships.
  4. Scaffolding no longer shows the child objects.

Lets solve these, one-at-a-time.

No longer have easy access to the collection of child items

This is easily fixed. We just add a new method to the parent object (Cart) that returns the collection of child objects (Item) that are in the cart:

grails-app\domain\shopping\Cart.groovy

    ....
    def getItems() {
    	Items.findAllByCart(this)
    }
    ....

Sorting of collections (defined in mapping) no longer takes place.

This is also easy. We just need a slight modification to the code above. We can add a sort parameter to the findAllBy* method.

grails-app\domain\shopping\Cart.groovy

    ....
    def getItems() {
        Item.findAllByCart(this, [sort:"name"])
    }
    ....

Now, we can access the cart’s items just like we did when we had a hasMany relationship.

    println cart.items

Can’t delete the parent object because of foreign-key relationships.

This was the head-scratcher. Not because it’s difficult, but it took a little research to find a elegant solution.

The problem is if we do a cart.delete(), it will fail it there are items in the cart. The old hasMany / belongsTo solution would automatically cascade-delete the items, and the cart. We don’t have that now, so we must do it manually.

The best solution that I know of (let me know if there is a better one) is to use the beforeDelete event on the Cart object.

Before a object is deleted, GORM fires the beforeDelete event. You can place code in it to do whatever you want. In our case, we will delete the child Item objects. Take a look at the documentation for beforeDelete .

Lets update the Cart.groovy code and add the beforeDelete method.

grails-app\domain\shopping\Cart.groovy

    ....
    def beforeDelete() {
        Item.withNewSession { items*.delete() }	
    }
    ....

(This is what I love about groovy – one line of code does so much work!)

First, we create a new hibernate session. If you read the docs for beforeDelete, you know that (in this case) you should use a new session for the delete in order to avoid StackOverflow exceptions.

Next, we delete all of the child items. We use groovy’s spread operation to call a method on all items in a list.

So, with that line of code, all child objects (Item) will be deleted when the parent object (Cart) is deleted.

Scaffolding no longer shows the child objects.

Since the parent object no longer knows about the child objects, the default grails scaffolding will now longer display the items in a cart. I don’t know a way around this, other than not using scaffolding.

Final Cart and Item Code

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {
	
	String name

	static mapping = {
		sort "name"
	}

	static constraints = {
		name nullable:false, blank:false, unique:true
	}

	String toString() {
		"Cart $name"
	}

	def getItems() {
		Item.findAllByCart(this, [sort:"name"])
	}

	def beforeDelete() {
		Item.withNewSession { items*.delete() }	
	}
    
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {

	String name
	Integer quantity
	Cart cart

	static mapping = {
		sort "name"
	}

	static constraints = {
		name nullable:false, blank:false, unique:['cart']
		quantity nullable:false
	}

	String toString() {
		"$quantity $name"
	}
}

My Opinions

Implementation Difficulty

Once I worked out how to implement this solution, installing it in my domain objects was pretty simple. Remember that you may need to update any code that uses addTo* and removeFrom* methods

Complexity / Maintenance

Does this solution make my apps more complex to understand? I don’t think so. A experienced groovy person can understand the cart.getItems() method. The beforeDelete will take a little time to understand, but it is quickly learned.

Also there is complexity in using belongsTo / hasMany. See GORM Gotchas Part 2. I don’t think this solution is any more complex the using belongsTo / hasMany.

Performance

Don’t know yet. I don’t have any real-world apps that I considered the loading of child objects to be a performance problem.

Would I Do It Again

Definitely. I had a project fall-apart because it has > 20 objects and >30 relationships between objects. Hibernate and GORM were causing various exceptions, and at the time I was too inexperienced to know how to solve the problems. I am considering attempting that project again, using this “no collections” technique to see if I can finish the project. I have a good feeling that this method will work, but I need to prove it.

Read Full Post | Make a Comment ( 23 so far )

A Pattern to Simplify Grails Controllers

Posted on January 23, 2011. Filed under: grails |

The WithDomainObject Pattern

Note, the sample code was modified on 5/18/2011. The withPerson method is now private.

I generally follow the same patterns with my controllers. They start with “the big 7 actions” – closures for index, list, show, create, save, edit, update and delete. Then i include any necessary controller-specific actions. After unit testing my controllers for the up-teenth time, I realized that there is a consistent pattern for many of the actions – get the id from params, get the domain object, then use the domain object. I wanted to extract this pattern into its own method, and that extraction evolved into the “WithDomainObject” pattern.

The Problem

Lets say you had a contact manager application, and you had a domain object called Person and a controller called PersonController. In the controllers show, save, edit, update and delete actions all follow the same pattern:

  1. Get the ID from the params map.
  2. Get the person from the domain.
  3. Doing something with the person.

The problem is that steps 1 and 2 are repeated for the 5 actions. Not very DRY.

The Solution

Lets encapsulate steps 1 and 2 into a method called withPerson:

	private def withPerson(id="id", Closure c) {
		def person = Person.get(params[id])
		if(person) {
			c.call person
		} else {
			flash.message = "The person was not found."
			redirect action:"list"
		}
	}
}

and lets put our code from step 3 into its own anonymous closure. Heres a example of a action that uses the withPerson method and the update action:

	def update = {
		withPerson { person ->
			person.properties = params
			if(person.validate() && person.save()) {
				redirect action:"show", id:person.id
			} else {
				render view:"edit", model:[person:person]
			}
		}
	}

Example

See the withPerson method at the botttom of the controller.

The Person domain class


package contact

class Person {

	String name

        static constraints = {
    	    name blank:false, unique:true
    }
}

The PersonController controller


package contact

class PersonController {

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

	def list = {
		[ people:Person.list(params), count:Person.count() ]
	}

	def show = {
		withPerson { person ->
			[person:person]
		}
	}

	def create = {
		[person:new Person()]
	}

	def save = {
		def person = new Person(params)
		if(person.validate() && person.save()) {
			redirect action:"show", id:person.id
		} else {
			render view:"create", model:[person:person]
		}
	}

	def edit = {
		withPerson { person ->
			[person:person]
		}
	}

	def update = {
		withPerson { person ->
			person.properties = params
			if(person.validate() && person.save()) {
				redirect action:"show", id:person.id
			} else {
				render view:"edit", model:[person:person]
			}
		}
	}

	def delete = {
		withPerson { person ->
			person.delete()
			redirect action:"list"
		}
	}

	private def withPerson(id="id", Closure c) {
		def person = Person.get(params[id])
		if(person) {
			c.call person
		} else {
			flash.message = "The person was not found."
			redirect action:"list"
		}
	}

}

The PersonControllerTests unit tests


package contact

import grails.test.*
import org.junit.*

class PersonControllerTests extends ControllerUnitTestCase {

    def p7 = new Person(id:7, name:"alpha")
    def p9 = new Person(id:9, name:"beta")

    @Before
    public void setUp() {
        super.setUp()
    	mockDomain Person, [p7,p9]
    }

    @After
    public void tearDown() {
        super.tearDown()
    }

    @Test
    public void index() {
		controller.index()
		assert "list" == controller.redirectArgs.action
    }

    @Test
    public void list() {
    	def model = controller.list()
    	assert 2 == model.people.size()
    	assert p7 == model.people[0]
    	assert p9 == model.people[1]
    	assert 2 == model.count
    }

    @Test
    public void show() {
    	controller.params.id = 7
    	def model = controller.show()
    	assert p7 == model.person
    }

    @Test
    public void create() {
    	def model = controller.create()
    	assert model.person instanceof Person
    }

    @Test
    public void save_success() {
    	controller.params.name = "Paul Woods"
    	controller.save()
    	assert "show" == controller.redirectArgs.action
    	assert null != controller.redirectArgs.id
    }

    @Test
    public void save_failure() {
    	controller.params.name = ""
    	controller.save()
    	assert "create" == controller.renderArgs.view
    	assert controller.renderArgs.model.person instanceof Person
    }

    @Test
    public void edit() {
    	controller.params.id = 9
    	def model = controller.edit()
    	assert p9 == model.person
    }

    @Test
    public void update_success() {
    	controller.params.name = "Paul Woods"
    	controller.params.id = 7
    	controller.update()
    	assert "show" == controller.redirectArgs.action
    	assert 7 == controller.redirectArgs.id
    }

    @Test
    public void update_failure() {
    	controller.params.name = ""
    	controller.params.id = 9
    	controller.update()
    	assert "edit" == controller.renderArgs.view
    	assert controller.renderArgs.model.person instanceof Person
    }

    @Test
    public void delete() {
    	controller.params.id = 7
    	controller.delete()
    	assert "list" == controller.redirectArgs.action
    	assert 1 == Person.count()
    }

    @Test
    public void withPerson_success() {
    	controller.params.id = 7
    	def person = null
    	controller.withPerson() { p ->
    		person = p
    	}

    	assert 7 == person.id
    }

    @Test
    public void withPerson_fail() {
    	controller.params.id = 0
    	controller.withPerson() { p ->
    		assert false
    	}
	assert "The person was not found." == controller.flash.message
	assert "list" == controller.redirectArgs.action
   }

}

Advantages of this Pattern:

  1. The controller actions are simpler, as they don’t require code to detect not-found domain objects.
  2. The unit tests can be simpler. There is less code to test.
Read Full Post | Make a Comment ( 25 so far )

Liked it here?
Why not try sites on the blogroll...